From fed67d2080e5181fb2d154d2dd52d1c7032bdfe0 Mon Sep 17 00:00:00 2001 From: maksis Date: Sat, 30 Jan 2016 20:06:42 +0200 Subject: [PATCH] Squashed 'airdcpp-webapi/' changes from 304979f..4804276 4804276 API: refactor view tasks, most likely fixes airdcpp-web/airdcpp-webclient#65 365f37f API: serialize encryption information for relevant entities, don't display CCPM connections in transfers, serialize connect state flags for OnlineUsers 225424c API: add transfer permission, show better initial status for transfers 2036ecc API: add transfer view c2e46af Merge branch 'master' of https://github.com/airdcpp/airgit 6f8a29a Hopefully fix crash stopping the web server fad4816 Compiling fixes for Boost 1.60 git-subtree-dir: airdcpp-webapi git-subtree-split: 4804276e4a36813cf46e5f82e4b70b81aff0e146 --- api/FilelistApi.cpp | 2 +- api/FilelistInfo.cpp | 6 +- api/HubApi.cpp | 2 +- api/HubInfo.cpp | 3 +- api/PrivateChatApi.cpp | 2 +- api/PrivateChatInfo.cpp | 3 +- api/QueueUtils.cpp | 35 ---- api/SearchApi.cpp | 2 +- api/ShareProfileApi.cpp | 2 +- api/ShareRootApi.cpp | 2 +- api/TransferApi.cpp | 334 +++++++++++++++++++++++++++++++- api/TransferApi.h | 87 ++++++++- api/TransferInfo.h | 111 +++++++++++ api/TransferUtils.cpp | 94 +++++++++ api/TransferUtils.h | 43 ++++ api/WebUserApi.cpp | 2 +- api/common/ListViewController.h | 109 +---------- api/common/Serializer.cpp | 7 + api/common/ViewTasks.h | 113 +++++++++++ web-server/Access.h | 1 + web-server/Session.cpp | 5 + web-server/WebServerManager.cpp | 4 +- web-server/WebUser.cpp | 1 + web-server/WebUserManager.cpp | 4 +- webapi.vcxproj | 4 + webapi.vcxproj.filters | 12 ++ 26 files changed, 834 insertions(+), 156 deletions(-) create mode 100644 api/TransferInfo.h create mode 100644 api/TransferUtils.cpp create mode 100644 api/TransferUtils.h create mode 100644 api/common/ViewTasks.h diff --git a/api/FilelistApi.cpp b/api/FilelistApi.cpp index 5eeb04b1..5608b4fa 100644 --- a/api/FilelistApi.cpp +++ b/api/FilelistApi.cpp @@ -54,7 +54,7 @@ namespace webserver { } void FilelistApi::addList(const DirectoryListingPtr& aList) noexcept { - addSubModule(aList->getUser()->getCID(), make_shared(this, aList)); + addSubModule(aList->getUser()->getCID(), std::make_shared(this, aList)); } api_return FilelistApi::handleQueueList(ApiRequest& aRequest, QueueItem::Flags aFlags) { diff --git a/api/FilelistInfo.cpp b/api/FilelistInfo.cpp index e4b0f669..814ce3f6 100644 --- a/api/FilelistInfo.cpp +++ b/api/FilelistInfo.cpp @@ -126,7 +126,7 @@ namespace webserver { return nullptr; } - auto ret = Serializer::serializeItem(make_shared(location.directory), itemHandler); + auto ret = Serializer::serializeItem(std::make_shared(location.directory), itemHandler); ret["size"] = location.totalSize; ret["complete"] = location.directory->isComplete(); @@ -145,11 +145,11 @@ namespace webserver { currentViewItems.clear(); for (auto& d : curDir->directories) { - currentViewItems.emplace_back(make_shared(d)); + currentViewItems.emplace_back(std::make_shared(d)); } for (auto& f : curDir->files) { - currentViewItems.emplace_back(make_shared(f)); + currentViewItems.emplace_back(std::make_shared(f)); } } diff --git a/api/HubApi.cpp b/api/HubApi.cpp index 4fa9442d..678b9da6 100644 --- a/api/HubApi.cpp +++ b/api/HubApi.cpp @@ -102,7 +102,7 @@ namespace webserver { } void HubApi::addHub(const ClientPtr& aClient) noexcept { - addSubModule(aClient->getClientId(), make_shared(this, aClient)); + addSubModule(aClient->getClientId(), std::make_shared(this, aClient)); } api_return HubApi::handleGetHubs(ApiRequest& aRequest) { diff --git a/api/HubInfo.cpp b/api/HubInfo.cpp index ed2bdd7c..090df818 100644 --- a/api/HubInfo.cpp +++ b/api/HubInfo.cpp @@ -158,7 +158,8 @@ namespace webserver { } return { - { "id", id } + { "id", id }, + { "encryption", aClient->getEncryptionInfo() }, }; } diff --git a/api/PrivateChatApi.cpp b/api/PrivateChatApi.cpp index 9863075b..0f5a6564 100644 --- a/api/PrivateChatApi.cpp +++ b/api/PrivateChatApi.cpp @@ -97,7 +97,7 @@ namespace webserver { } void PrivateChatApi::addChat(const PrivateChatPtr& aChat) noexcept { - addSubModule(aChat->getUser()->getCID(), make_shared(this, aChat)); + addSubModule(aChat->getUser()->getCID(), std::make_shared(this, aChat)); } void PrivateChatApi::on(MessageManagerListener::ChatCreated, const PrivateChatPtr& aChat, bool aReceivedMessage) noexcept { diff --git a/api/PrivateChatInfo.cpp b/api/PrivateChatInfo.cpp index 7561c87d..74dba068 100644 --- a/api/PrivateChatInfo.cpp +++ b/api/PrivateChatInfo.cpp @@ -85,7 +85,8 @@ namespace webserver { return{ { "id", formatCCPMState(aChat->getCCPMState()) }, { "str", PrivateChat::ccpmStateToString(aChat->getCCPMState()) }, - { "supported", aChat->getSupportsCCPM() } + { "supported", aChat->getSupportsCCPM() }, + { "encryption", aChat->getUc() ? aChat->getUc()->getEncryptionInfo() : Util::emptyString }, }; } diff --git a/api/QueueUtils.cpp b/api/QueueUtils.cpp index 5f11614b..2ad08033 100644 --- a/api/QueueUtils.cpp +++ b/api/QueueUtils.cpp @@ -74,41 +74,6 @@ namespace webserver { } } - /*json QueueUtils::serializeQueueItem(const QueueItemPtr& aQI) noexcept { - json j; - return{ - { "user", aQI-> }, - { "file_count", aSource.files }, - { "bytes_left", aSource.size } - }; - - return j; - } - - json QueueUtils::serializeQueueItemBase(const QueueItemBase& aItem) noexcept { - return{ - { "target", aItem.getTarget() }, - { "size", aItem.getSize() }, - { "time_added", aItem.getTimeAdded() }, - { "priority", aItem.getPriority() }, - { "using_autopriority", aItem.getAutoPriority() } - }; - }*/ - - /*json QueueUtils::serializeBundleSource(const Bundle::BundleSource& aSource) noexcept { - return{ - { "user", serializeUser(aSource.getUser()) }, - { "file_count", aSource.files }, - { "bytes_left", aSource.size } - }; - } - - json QueueUtils::serializeQueueItemSource(const QueueItem::Source& aSource) noexcept { - return{ - { "user", serializeUser(aSource.getUser()) } - }; - }*/ - json QueueUtils::serializePriority(const QueueItemBase& aItem) noexcept { return{ { "id", aItem.getPriority() }, diff --git a/api/SearchApi.cpp b/api/SearchApi.cpp index d06c415a..ca8341b4 100644 --- a/api/SearchApi.cpp +++ b/api/SearchApi.cpp @@ -192,7 +192,7 @@ namespace webserver { } SearchResultInfoPtr parent = nullptr; - auto result = make_shared(aResult, move(*relevancyInfo)); + auto result = std::make_shared(aResult, move(*relevancyInfo)); { WLock l(cs); diff --git a/api/ShareProfileApi.cpp b/api/ShareProfileApi.cpp index 1ad6fe7a..aa731f02 100644 --- a/api/ShareProfileApi.cpp +++ b/api/ShareProfileApi.cpp @@ -106,7 +106,7 @@ namespace webserver { api_return ShareProfileApi::handleAddProfile(ApiRequest& aRequest) { const auto& reqJson = aRequest.getRequestBody(); - auto profile = make_shared(); + auto profile = std::make_shared(); parseProfile(profile, reqJson); ShareManager::getInstance()->addProfile(profile); diff --git a/api/ShareRootApi.cpp b/api/ShareRootApi.cpp index 577d7f13..cead9e39 100644 --- a/api/ShareRootApi.cpp +++ b/api/ShareRootApi.cpp @@ -79,7 +79,7 @@ namespace webserver { JsonUtil::throwError("path", JsonUtil::ERROR_INVALID, e.what()); } - auto info = make_shared(path); + auto info = std::make_shared(path); parseRoot(info, reqJson, true); diff --git a/api/TransferApi.cpp b/api/TransferApi.cpp index aa208e06..37ecc95d 100644 --- a/api/TransferApi.cpp +++ b/api/TransferApi.cpp @@ -20,19 +20,41 @@ #include #include +#include #include #include +#include +#include #include namespace webserver { - TransferApi::TransferApi(Session* aSession) : ApiModule(aSession, Access::ANY), timer(getTimer([this] { onTimer(); }, 1000)) { + TransferApi::TransferApi(Session* aSession) : + ApiModule(aSession, Access::ANY), + timer(getTimer([this] { onTimer(); }, 1000)), + propertyHandler(properties, + TransferUtils::getStringInfo, TransferUtils::getNumericInfo, TransferUtils::compareItems, TransferUtils::serializeProperty), + view("transfer_view", this, propertyHandler, std::bind(&TransferApi::getTransfers, this)) + { + + view.setActiveStateChangeHandler([&](bool aActive) { + if (aActive) { + loadTransfers(); + } else { + unloadTransfers(); + } + }); + DownloadManager::getInstance()->addListener(this); UploadManager::getInstance()->addListener(this); + ConnectionManager::getInstance()->addListener(this); METHOD_HANDLER("stats", Access::ANY, ApiRequest::METHOD_GET, (), false, TransferApi::handleGetStats); + METHOD_HANDLER("force", Access::TRANSFERS, ApiRequest::METHOD_POST, (TOKEN_PARAM), false, TransferApi::handleForce); + METHOD_HANDLER("disconnect", Access::TRANSFERS, ApiRequest::METHOD_POST, (TOKEN_PARAM), false, TransferApi::handleDisconnect); + createSubscription("transfer_statistics"); timer->start(false); } @@ -42,6 +64,58 @@ namespace webserver { DownloadManager::getInstance()->removeListener(this); UploadManager::getInstance()->removeListener(this); + ConnectionManager::getInstance()->removeListener(this); + } + + void TransferApi::loadTransfers() noexcept { + // add the existing connections + { + auto cm = ConnectionManager::getInstance(); + RLock l(cm->getCS()); + for (const auto& d : cm->getTransferConnections(true)) { + auto info = addTransfer(d, "Inactive, waiting for status updates"); + updateQueueInfo(info); + } + + for (const auto& u : cm->getTransferConnections(false)) { + addTransfer(u, "Inactive, waiting for status updates"); + } + } + + { + auto um = UploadManager::getInstance(); + RLock l(um->getCS()); + for (const auto& u : um->getUploads()) { + if (u->getUserConnection().getState() == UserConnection::STATE_RUNNING) { + on(UploadManagerListener::Starting(), u); + } + } + } + + { + auto dm = DownloadManager::getInstance(); + RLock l(dm->getCS()); + for (const auto& d : dm->getDownloads()) { + if (d->getUserConnection().getState() == UserConnection::STATE_RUNNING) { + starting(d, "Downloading", true); + } + } + } + } + + void TransferApi::unloadTransfers() noexcept { + WLock l(cs); + transfers.clear(); + } + + TransferInfo::List TransferApi::getTransfers() const noexcept { + TransferInfo::List ret; + { + RLock l(cs); + boost::range::copy(transfers | map_values, back_inserter(ret)); + } + + return ret; } api_return TransferApi::handleGetStats(ApiRequest& aRequest) { @@ -55,6 +129,39 @@ namespace webserver { return websocketpp::http::status_code::ok; } + api_return TransferApi::handleForce(ApiRequest& aRequest) { + auto item = getTransfer(aRequest); + if (item->isDownload()) { + ConnectionManager::getInstance()->force(item->getStringToken()); + } + + return websocketpp::http::status_code::ok; + } + + api_return TransferApi::handleDisconnect(ApiRequest& aRequest) { + auto item = getTransfer(aRequest); + ConnectionManager::getInstance()->disconnect(item->getStringToken()); + + return websocketpp::http::status_code::ok; + } + + TransferInfoPtr TransferApi::getTransfer(ApiRequest& aRequest) const { + // This isn't used ofter + + auto wantedId = aRequest.getTokenParam(0); + + RLock l(cs); + auto ret = boost::find_if(transfers | map_values, [&](const TransferInfoPtr& aInfo) { + return aInfo->getToken() == wantedId; + }); + + if (ret.base() == transfers.end()) { + throw std::invalid_argument("Transfer not found"); + } + + return *ret; + } + void TransferApi::onTimer() { if (!subscriptionActive("transfer_statistics")) return; @@ -95,12 +202,43 @@ namespace webserver { send("transfer_statistics", j); } + void TransferApi::onTick(const Transfer* aTransfer, bool aIsDownload) noexcept { + auto t = getTransfer(aTransfer->getToken()); + if (!t) { + return; + } + + t->setSpeed(aTransfer->getAverageSpeed()); + t->setBytesTransferred(aTransfer->getPos()); + t->setTimeLeft(aTransfer->getSecondsLeft()); + + uint64_t timeSinceStarted = GET_TICK() - t->getStarted(); + if (timeSinceStarted < 1000) { + t->setStatusString(STRING(DOWNLOAD_STARTING)); + } else { + auto pct = t->getSize() > 0 ? (double)t->getBytesTransferred() * 100.0 / (double)t->getSize() : 0; + t->setStatusString(STRING_F(RUNNING_PCT, pct)); + } + + view.onItemUpdated(t, { PROP_STATUS, PROP_BYTES_TRANSFERRED, PROP_SPEED, PROP_SECONDS_LEFT }); + } + void TransferApi::on(UploadManagerListener::Tick, const UploadList& aUploads) noexcept { lastUploads = aUploads.size(); + + for (const auto& ul : aUploads) { + if (ul->getPos() == 0) continue; + + onTick(ul, false); + } } void TransferApi::on(DownloadManagerListener::Tick, const DownloadList& aDownloads) noexcept { lastDownloads = aDownloads.size(); + + for (const auto& dl : aDownloads) { + onTick(dl, true); + } } void TransferApi::on(DownloadManagerListener::BundleTick, const BundleList& bundles, uint64_t aTick) noexcept { @@ -110,4 +248,198 @@ namespace webserver { void TransferApi::on(UploadManagerListener::BundleTick, const UploadBundleList& bundles) noexcept { lastUploadBundles = bundles.size(); } + + TransferInfoPtr TransferApi::addTransfer(const ConnectionQueueItem* aCqi, const string& aStatus) noexcept { + auto t = std::make_shared(aCqi->getHintedUser(), aCqi->getConnType() == ConnectionType::CONNECTION_TYPE_DOWNLOAD, aCqi->getToken()); + + { + WLock l(cs); + transfers[aCqi->getToken()] = t; + } + + t->setStatusString(aStatus); + return t; + } + + void TransferApi::on(ConnectionManagerListener::Added, const ConnectionQueueItem* aCqi) noexcept { + if (aCqi->getConnType() == CONNECTION_TYPE_PM) + return; + + auto t = addTransfer(aCqi, STRING(CONNECTING)); + view.onItemAdded(t); + } + + void TransferApi::on(ConnectionManagerListener::Removed, const ConnectionQueueItem* aCqi) noexcept { + TransferInfoPtr t; + + { + WLock l(cs); + auto i = transfers.find(aCqi->getToken()); + if (i == transfers.end()) { + return; + } + + t = i->second; + transfers.erase(i); + } + + view.onItemRemoved(t); + } + + void TransferApi::onFailed(TransferInfoPtr& aInfo, const string& aReason) noexcept { + if (aInfo->getState() == TransferInfo::STATE_FAILED) { + // The connection is disconnected right after download fails, which causes double events + // Don't override the previous message + return; + } + + aInfo->setStatusString(aReason); + aInfo->setSpeed(-1); + aInfo->setBytesTransferred(-1); + aInfo->setTimeLeft(-1); + aInfo->setState(TransferInfo::STATE_FAILED); + + view.onItemUpdated(aInfo, { PROP_STATUS, PROP_SPEED, PROP_BYTES_TRANSFERRED, PROP_SECONDS_LEFT }); + } + + void TransferApi::on(ConnectionManagerListener::Failed, const ConnectionQueueItem* aCqi, const string& aReason) noexcept { + auto t = getTransfer(aCqi->getToken()); + if (!t) { + return; + } + + onFailed(t, aCqi->getUser()->isSet(User::OLD_CLIENT) ? STRING(SOURCE_TOO_OLD) : aReason); + } + + void TransferApi::updateQueueInfo(TransferInfoPtr& aInfo) noexcept { + QueueToken bundleToken = 0; + string aTarget; + int64_t aSize; int aFlags = 0; + if (!QueueManager::getInstance()->getQueueInfo(aInfo->getHintedUser(), aTarget, aSize, aFlags, bundleToken)) { + return; + } + + auto type = Transfer::TYPE_FILE; + if (aFlags & QueueItem::FLAG_PARTIAL_LIST) + type = Transfer::TYPE_PARTIAL_LIST; + else if (aFlags & QueueItem::FLAG_USER_LIST) + type = Transfer::TYPE_FULL_LIST; + + aInfo->setType(type); + aInfo->setTarget(aTarget); + aInfo->setSize(aSize); + + aInfo->setState(TransferInfo::STATE_WAITING); + aInfo->setStatusString(STRING(CONNECTING)); + + aInfo->setState(TransferInfo::STATE_WAITING); + + view.onItemUpdated(aInfo, { PROP_STATUS, PROP_TARGET, PROP_NAME, PROP_SIZE }); + } + + void TransferApi::on(ConnectionManagerListener::Connecting, const ConnectionQueueItem* aCqi) noexcept { + auto t = getTransfer(aCqi->getToken()); + if (!t) { + return; + } + + updateQueueInfo(t); + } + + void TransferApi::on(ConnectionManagerListener::UserUpdated, const ConnectionQueueItem* aCqi) noexcept { + auto t = getTransfer(aCqi->getToken()); + if (!t) { + return; + } + + view.onItemUpdated(t, { PROP_USER }); + } + + void TransferApi::on(DownloadManagerListener::Failed, const Download* aDownload, const string& aReason) noexcept { + auto t = getTransfer(aDownload->getToken()); + if (!t) { + return; + } + + auto status = aReason; + if (aDownload->isSet(Download::FLAG_SLOWUSER)) { + status += ": " + STRING(SLOW_USER); + } else if (aDownload->getOverlapped() && !aDownload->isSet(Download::FLAG_OVERLAP)) { + status += ": " + STRING(OVERLAPPED_SLOW_SEGMENT); + } + + onFailed(t, status); + } + + void TransferApi::starting(TransferInfoPtr& aInfo, const Transfer* aTransfer) noexcept { + aInfo->setBytesTransferred(aTransfer->getPos()); + aInfo->setTarget(aTransfer->getPath()); + aInfo->setStarted(GET_TICK()); + aInfo->setState(TransferInfo::STATE_RUNNING); + aInfo->setIp(aTransfer->getUserConnection().getRemoteIp()); + aInfo->setType(aTransfer->getType()); + aInfo->setEncryption(aTransfer->getUserConnection().getEncryptionInfo()); + + view.onItemUpdated(aInfo, { PROP_STATUS, PROP_SPEED, PROP_BYTES_TRANSFERRED, PROP_TIME_STARTED, PROP_SIZE, PROP_TARGET, PROP_NAME, PROP_IP, PROP_ENCRYPTION, PROP_FLAGS }); + } + + void TransferApi::on(DownloadManagerListener::Requesting, const Download* aDownload, bool hubChanged) noexcept { + starting(aDownload, STRING(REQUESTING), true); + } + + void TransferApi::starting(const Download* aDownload, const string& aStatus, bool aFullUpdate) noexcept { + auto t = getTransfer(aDownload->getToken()); + if (!t) { + return; + } + + t->setSize(aDownload->getSegmentSize()); + t->setStatusString(aStatus); + + OrderedStringSet flags; + aDownload->appendFlags(flags); + t->setFlags(flags); + + if (aFullUpdate) { + starting(t, aDownload); + } else { + view.onItemUpdated(t, { PROP_STATUS, PROP_SIZE, PROP_FLAGS }); + } + } + + void TransferApi::on(DownloadManagerListener::Starting, const Download* aDownload) noexcept { + // No need for full update as it's done in the requesting phase + starting(aDownload, STRING(DOWNLOAD_STARTING), false); + } + + void TransferApi::on(UploadManagerListener::Starting, const Upload* aUpload) noexcept { + auto t = getTransfer(aUpload->getToken()); + if (!t) { + return; + } + + t->setSize(aUpload->getType() == Transfer::TYPE_TREE ? aUpload->getSegmentSize() : aUpload->getFileSize()); + starting(t, aUpload); + } + + TransferInfoPtr TransferApi::getTransfer(const string& aToken) const noexcept { + RLock l(cs); + auto i = transfers.find(aToken); + return i != transfers.end() ? i->second : nullptr; + } + + void TransferApi::onTransferCompleted(const Transfer* aTransfer, bool aIsDownload) noexcept { + auto t = getTransfer(aTransfer->getToken()); + if (!t) { + return; + } + + t->setStatusString(aIsDownload ? STRING(DOWNLOAD_FINISHED_IDLE) : STRING(UPLOAD_FINISHED_IDLE)); + t->setSpeed(-1); + t->setTimeLeft(-1); + t->setBytesTransferred(aTransfer->getSegmentSize()); + t->setState(TransferInfo::STATE_FINISHED); + + view.onItemUpdated(t, { PROP_STATUS, PROP_SPEED, PROP_SECONDS_LEFT, PROP_TIME_STARTED, PROP_BYTES_TRANSFERRED }); + } } \ No newline at end of file diff --git a/api/TransferApi.h b/api/TransferApi.h index e8eefa3a..c18799bc 100644 --- a/api/TransferApi.h +++ b/api/TransferApi.h @@ -22,13 +22,22 @@ #include #include +#include +#include #include + +#include +#include +#include +#include + +#include #include #include namespace webserver { - class TransferApi : public ApiModule, private DownloadManagerListener, private UploadManagerListener { + class TransferApi : public ApiModule, private ConnectionManagerListener, private DownloadManagerListener, private UploadManagerListener { public: TransferApi(Session* aSession); ~TransferApi(); @@ -36,16 +45,84 @@ namespace webserver { int getVersion() const noexcept { return 0; } + + const PropertyList properties = { + { PROP_NAME, "name", TYPE_TEXT, SERIALIZE_TEXT, SORT_TEXT }, + { PROP_TARGET, "target", TYPE_TEXT, SERIALIZE_TEXT, SORT_TEXT }, + { PROP_DOWNLOAD, "download", TYPE_NUMERIC_OTHER, SERIALIZE_BOOL, SORT_NUMERIC }, + { PROP_SIZE, "size", TYPE_SIZE, SERIALIZE_NUMERIC, SORT_NUMERIC }, + { PROP_STATUS, "status", TYPE_TEXT, SERIALIZE_CUSTOM, SORT_CUSTOM }, + { PROP_BYTES_TRANSFERRED, "bytes_transferred", TYPE_SIZE, SERIALIZE_NUMERIC, SORT_NUMERIC }, + { PROP_USER, "user", TYPE_TEXT, SERIALIZE_CUSTOM, SORT_CUSTOM }, + { PROP_TIME_STARTED, "time_started", TYPE_TIME, SERIALIZE_NUMERIC, SORT_NUMERIC }, + { PROP_SPEED, "speed", TYPE_SPEED, SERIALIZE_NUMERIC, SORT_NUMERIC }, + { PROP_SECONDS_LEFT, "seconds_left", TYPE_TIME, SERIALIZE_NUMERIC, SORT_NUMERIC }, + { PROP_IP, "ip", TYPE_TEXT, SERIALIZE_CUSTOM, SORT_TEXT }, + { PROP_FLAGS, "flags", TYPE_LIST_TEXT, SERIALIZE_CUSTOM, SORT_CUSTOM }, + { PROP_ENCRYPTION, "encryption", TYPE_TEXT, SERIALIZE_TEXT, SORT_TEXT }, + }; + + enum Properties { + PROP_TOKEN = -1, + PROP_NAME, + PROP_TARGET, + PROP_DOWNLOAD, + PROP_SIZE, + PROP_STATUS, + //PROP_STATE, + PROP_BYTES_TRANSFERRED, + PROP_USER, + PROP_TIME_STARTED, + PROP_SPEED, + PROP_SECONDS_LEFT, + PROP_IP, + PROP_FLAGS, + PROP_ENCRYPTION, + PROP_LAST + }; private: + void loadTransfers() noexcept; + void unloadTransfers() noexcept; + api_return handleGetStats(ApiRequest& aRequest); + api_return handleForce(ApiRequest& aRequest); + api_return handleDisconnect(ApiRequest& aRequest); + + TransferInfoPtr getTransfer(ApiRequest& aRequest) const; + TransferInfoPtr getTransfer(const string& aToken) const noexcept; + TransferInfo::List getTransfers() const noexcept; + TransferInfoPtr addTransfer(const ConnectionQueueItem* aCqi, const string& aStatus) noexcept; + void onTimer(); + void onFailed(TransferInfoPtr& aInfo, const string& aReason) noexcept; + void starting(const Download* aDownload, const string& aStatus, bool aFullUpdate) noexcept; + void starting(TransferInfoPtr& aInfo, const Transfer* aTransfer) noexcept; + void onTransferCompleted(const Transfer* aTransfer, bool aIsDownload) noexcept; + void onTick(const Transfer* aTransfer, bool aIsDownload) noexcept; + void updateQueueInfo(TransferInfoPtr& aInfo) noexcept; + void on(DownloadManagerListener::Tick, const DownloadList& aDownloads) noexcept; void on(DownloadManagerListener::BundleTick, const BundleList& bundles, uint64_t aTick) noexcept; void on(UploadManagerListener::Tick, const UploadList& aUploads) noexcept; void on(UploadManagerListener::BundleTick, const UploadBundleList& bundles) noexcept; + void on(ConnectionManagerListener::Added, const ConnectionQueueItem* aCqi) noexcept; + void on(ConnectionManagerListener::Removed, const ConnectionQueueItem* aCqi) noexcept; + void on(ConnectionManagerListener::Failed, const ConnectionQueueItem* aCqi, const string &reason) noexcept; + void on(ConnectionManagerListener::Connecting, const ConnectionQueueItem* aCqi) noexcept; + void on(ConnectionManagerListener::UserUpdated, const ConnectionQueueItem* aCqi) noexcept; + + void on(DownloadManagerListener::Starting, const Download* aDownload) noexcept; + void on(DownloadManagerListener::Complete, const Download* aDownload, bool) noexcept { onTransferCompleted(aDownload, true); } + void on(DownloadManagerListener::Failed, const Download* aDownload, const string &reason) noexcept; + void on(DownloadManagerListener::Requesting, const Download* aDownload, bool hubChanged) noexcept; + + void on(UploadManagerListener::Starting, const Upload* aUpload) noexcept; + void on(UploadManagerListener::Complete, const Upload* aUpload) noexcept { onTransferCompleted(aUpload, false); } + + json previousStats; int lastUploadBundles = 0; @@ -55,6 +132,14 @@ namespace webserver { int lastDownloads = 0; TimerPtr timer; + + mutable SharedMutex cs; + TransferInfo::Map transfers; + + PropertyItemHandler propertyHandler; + + typedef ListViewController TransferListView; + TransferListView view; }; } diff --git a/api/TransferInfo.h b/api/TransferInfo.h new file mode 100644 index 00000000..87acd062 --- /dev/null +++ b/api/TransferInfo.h @@ -0,0 +1,111 @@ +/* +* Copyright (C) 2011-2016 AirDC++ Project +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program 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 General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#ifndef DCPLUSPLUS_DCPP_TRANSFERINFO_H +#define DCPLUSPLUS_DCPP_TRANSFERINFO_H + +#include + +#include +#include +#include + +namespace webserver { + typedef uint32_t TransferToken; + class TransferInfo { + public: + enum ItemState { + STATE_WAITING, + STATE_RUNNING, + STATE_FAILED, + STATE_FINISHED, + STATE_LAST, + }; + + typedef shared_ptr Ptr; + typedef vector List; + typedef unordered_map Map; + + TransferInfo(const HintedUser& aUser, bool aIsDownload, const std::string& aToken) : + user(aUser), download(aIsDownload), stringToken(aToken) + { } + + IGETSET(int64_t, timeLeft, TimeLeft, -1); + IGETSET(int64_t, size, Size, -1); + + GETSET(string, encryption, Encryption); + GETSET(string, ip, Ip); + GETSET(string, target, Target); + GETSET(string, statusString, StatusString); + GETSET(OrderedStringSet, flags, Flags); + + IGETSET(Transfer::Type, type, Type, Transfer::TYPE_LAST) + + IGETSET(int64_t, started, Started, 0); + IGETSET(int64_t, bytesTransferred, BytesTransferred, -1); + IGETSET(int64_t, speed, Speed, 0); + IGETSET(ItemState, state, State, STATE_WAITING); + + const TransferToken getToken() const noexcept { + return token; + } + + const string& getStringToken() const noexcept { + return stringToken; + } + + const bool isDownload() const noexcept { + return download; + } + + const HintedUser& getHintedUser() const noexcept { + return user; + } + + string getName() { + switch (type) { + case Transfer::TYPE_TREE: return "TTH: " + Util::getFileName(target); + case Transfer::TYPE_FULL_LIST: return STRING(FILE_LIST); + case Transfer::TYPE_PARTIAL_LIST: return STRING(FILE_LIST_PARTIAL); + default: return Util::getFileName(target); + } + } + + string getStateKey() { + switch (state) { + case STATE_WAITING: return "waiting"; + case STATE_FINISHED: return "finished"; + case STATE_RUNNING: return "running"; + case STATE_FAILED: return "failed"; + default: dcassert(0); return Util::emptyString; + } + } + private: + const HintedUser user; + const bool download; + + const TransferToken token = Util::rand(); + const std::string stringToken; + + bool transferFailed = false; + }; + + typedef TransferInfo::Ptr TransferInfoPtr; +} + +#endif \ No newline at end of file diff --git a/api/TransferUtils.cpp b/api/TransferUtils.cpp new file mode 100644 index 00000000..4ff7b636 --- /dev/null +++ b/api/TransferUtils.cpp @@ -0,0 +1,94 @@ +/* +* Copyright (C) 2011-2016 AirDC++ Project +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program 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 General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#include + +#include + +#include +#include + + +namespace webserver { + std::string TransferUtils::getStringInfo(const TransferInfoPtr& aItem, int aPropertyName) noexcept { + switch (aPropertyName) { + case TransferApi::PROP_NAME: return aItem->getName(); + case TransferApi::PROP_TARGET: return aItem->getTarget(); + case TransferApi::PROP_STATUS: return aItem->getStatusString(); + case TransferApi::PROP_IP: return aItem->getIp(); + case TransferApi::PROP_USER: return Format::formatNicks(aItem->getHintedUser()); + case TransferApi::PROP_ENCRYPTION: return aItem->getEncryption(); + default: dcassert(0); return Util::emptyString; + } + } + + double TransferUtils::getNumericInfo(const TransferInfoPtr& aItem, int aPropertyName) noexcept { + switch (aPropertyName) { + case TransferApi::PROP_SIZE: return (double)aItem->getSize(); + case TransferApi::PROP_DOWNLOAD: return (double)aItem->isDownload(); + case TransferApi::PROP_STATUS: return (double)aItem->getState(); + case TransferApi::PROP_BYTES_TRANSFERRED: return (double)aItem->getBytesTransferred(); + case TransferApi::PROP_TIME_STARTED: return (double)aItem->getStarted(); + case TransferApi::PROP_SPEED: return (double)aItem->getSpeed(); + case TransferApi::PROP_SECONDS_LEFT: return (double)aItem->getTimeLeft(); + default: dcassert(0); return 0; + } + } + + int TransferUtils::compareItems(const TransferInfoPtr& a, const TransferInfoPtr& b, int aPropertyName) noexcept { + switch (aPropertyName) { + case TransferApi::PROP_FLAGS: { + return compare(Util::listToString(a->getFlags()), Util::listToString(b->getFlags())); + } + case TransferApi::PROP_USER: { + if (a->isDownload() != b->isDownload()) { + return a->isDownload() ? -1 : 1; + } + + return Util::stricmp(Format::formatNicks(a->getHintedUser()), Format::formatNicks(b->getHintedUser())); + } + case TransferApi::PROP_STATUS: { + if (a->getState() != b->getState()) { + return compare(a->getState(), b->getState()); + } + + return Util::stricmp(a->getStatusString(), b->getStatusString()); + } + default: dcassert(0); return 0; + } + return 0; + } + + json TransferUtils::serializeProperty(const TransferInfoPtr& aItem, int aPropertyName) noexcept { + switch (aPropertyName) { + case TransferApi::PROP_IP: return Serializer::serializeIp(aItem->getIp()); + case TransferApi::PROP_USER: return Serializer::serializeHintedUser(aItem->getHintedUser()); + case TransferApi::PROP_STATUS: + { + return { + { "id", aItem->getStateKey() }, + { "str", aItem->getStatusString() }, + }; + } + case TransferApi::PROP_FLAGS: return aItem->getFlags(); + } + + dcassert(0); + return json(); + } +} \ No newline at end of file diff --git a/api/TransferUtils.h b/api/TransferUtils.h new file mode 100644 index 00000000..b6faa5a1 --- /dev/null +++ b/api/TransferUtils.h @@ -0,0 +1,43 @@ +/* +* Copyright (C) 2011-2016 AirDC++ Project +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program 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 General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#ifndef DCPLUSPLUS_DCPP_TRANSFERUTILS_H +#define DCPLUSPLUS_DCPP_TRANSFERUTILS_H + +#include + +//#include + +#include + +namespace webserver { + class TransferUtils { + public: + static json serializeProperty(const TransferInfoPtr& aItem, int aPropertyName) noexcept; + + static int compareItems(const TransferInfoPtr& a, const TransferInfoPtr& b, int aPropertyName) noexcept; + + static std::string getStringInfo(const TransferInfoPtr& aItem, int aPropertyName) noexcept; + static double getNumericInfo(const TransferInfoPtr& aItem, int aPropertyName) noexcept; + + private: + + }; +} + +#endif \ No newline at end of file diff --git a/api/WebUserApi.cpp b/api/WebUserApi.cpp index 1288502b..8751c7f5 100644 --- a/api/WebUserApi.cpp +++ b/api/WebUserApi.cpp @@ -62,7 +62,7 @@ namespace webserver { auto userName = JsonUtil::getField("username", reqJson, false); - auto user = make_shared(userName, Util::emptyString); + auto user = std::make_shared(userName, Util::emptyString); parseUser(user, reqJson, true); diff --git a/api/common/ListViewController.h b/api/common/ListViewController.h index 82ec8e7d..efb23781 100644 --- a/api/common/ListViewController.h +++ b/api/common/ListViewController.h @@ -26,12 +26,12 @@ #include #include -#include #include #include #include #include +#include namespace webserver { @@ -171,7 +171,7 @@ namespace webserver { } PropertyFilter::Ptr addFilter() { - auto filter = make_shared(itemHandler.properties); + auto filter = std::make_shared(itemHandler.properties); { WLock l(cs); @@ -432,100 +432,8 @@ namespace webserver { } // TASKS START - - class ItemTasks { - public: - struct MergeTask { - int8_t type; - PropertyIdSet updatedProperties; - - MergeTask(int8_t aType, const PropertyIdSet& aUpdatedProperties = PropertyIdSet()) : type(aType), updatedProperties(aUpdatedProperties) { - - } - - void merge(const MergeTask& aTask) { - // Ignore - if (type > aTask.type) { - return; - } - - // Merge - if (type == aTask.type) { - updatedProperties.insert(aTask.updatedProperties.begin(), aTask.updatedProperties.end()); - return; - } - - // Replace the task - type = aTask.type; - updatedProperties = aTask.updatedProperties; - } - }; - - typedef map TaskMap; - - void add(const T& aItem, MergeTask&& aData) { - WLock l(cs); - auto j = tasks.find(aItem); - if (j != tasks.end()) { - (*j).second.merge(aData); - return; - } - - tasks.emplace(aItem, move(aData)); - } - - void clear() { - WLock l(cs); - tasks.clear(); - } - - bool remove(const T& aItem) { - WLock l(cs); - return tasks.erase(aItem) > 0; - } - - void get(TaskMap& map) { - WLock l(cs); - swap(tasks, map); - } - private: - TaskMap tasks; - - SharedMutex cs; - }; - - class ViewTasks : public ItemTasks { - public: - void addItem(const T& aItem) { - tasks.add(aItem, typename ViewTasks::MergeTask(ADD_ITEM)); - } - - void removeItem(const T& aItem) { - tasks.add(aItem, typename ViewTasks::MergeTask(REMOVE_ITEM)); - } - - void updateItem(const T& aItem, const PropertyIdSet& aUpdatedProperties) { - updatedProperties.insert(aUpdatedProperties.begin(), aUpdatedProperties.end()); - tasks.add(aItem, typename ViewTasks::MergeTask(UPDATE_ITEM, aUpdatedProperties)); - } - - void get(typename ItemTasks::TaskMap& map, PropertyIdSet& updatedProperties_) { - tasks.get(map); - updatedProperties_.swap(updatedProperties); - } - - void clear() { - updatedProperties.clear(); - tasks.clear(); - } - private: - PropertyIdSet updatedProperties; - ItemTasks tasks; - }; - - void runTasks() { - typename ViewTasks::TaskMap currentTasks; + typename ItemTasks::TaskMap currentTasks; PropertyIdSet updatedProperties; tasks.get(currentTasks, updatedProperties); @@ -595,7 +503,7 @@ namespace webserver { } typedef std::map ItemPropertyIdMap; - ItemPropertyIdMap handleTasks(const typename ViewTasks::TaskMap& aTaskList, int aSortProperty, int aSortAscending, int& rangeStart_) { + ItemPropertyIdMap handleTasks(const typename ItemTasks::TaskMap& aTaskList, int aSortProperty, int aSortAscending, int& rangeStart_) { ItemPropertyIdMap updatedItems; for (auto& t : aTaskList) { switch (t.second.type) { @@ -810,17 +718,10 @@ namespace webserver { ApiModule* module = nullptr; std::string viewName; - // Must be in merging order (lower ones replace other) - enum Tasks { - UPDATE_ITEM = 0, - ADD_ITEM, - REMOVE_ITEM - }; + ItemTasks tasks; TimerPtr timer; - ViewTasks tasks; - class IntCollector { public: enum ValueType { diff --git a/api/common/Serializer.cpp b/api/common/Serializer.cpp index f6c3ae81..0dac643f 100644 --- a/api/common/Serializer.cpp +++ b/api/common/Serializer.cpp @@ -87,6 +87,13 @@ namespace webserver { if (aUser->isHidden()) { flags_.insert("hidden"); } + + auto cm = aUser->getIdentity().getConnectMode(); + if (cm == Identity::MODE_NOCONNECT_PASSIVE || cm == Identity::MODE_NOCONNECT_IP || cm == Identity::MODE_UNDEFINED) { + flags_.insert("noconnect"); + } if (!aUser->getIdentity().isTcpActive(aUser->getClient())) { + flags_.insert("passive"); + } } json Serializer::serializeUser(const UserPtr& aUser) noexcept { diff --git a/api/common/ViewTasks.h b/api/common/ViewTasks.h new file mode 100644 index 00000000..e1541a3c --- /dev/null +++ b/api/common/ViewTasks.h @@ -0,0 +1,113 @@ +/* +* Copyright (C) 2011-2016 AirDC++ Project +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program 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 General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#ifndef DCPLUSPLUS_DCPP_VIEWTASKS_H +#define DCPLUSPLUS_DCPP_VIEWTASKS_H + +#include + +#include + + +namespace webserver { + +// Must be in merging order (lower ones replace other) +enum Tasks { + UPDATE_ITEM = 0, + ADD_ITEM, + REMOVE_ITEM +}; + + +template +class ItemTasks { +public: + struct MergeTask { + int8_t type; + PropertyIdSet updatedProperties; + + MergeTask(int8_t aType, const PropertyIdSet& aUpdatedProperties = PropertyIdSet()) : type(aType), updatedProperties(aUpdatedProperties) { + + } + + void merge(const MergeTask& aTask) { + // Ignore + if (type > aTask.type) { + return; + } + + // Merge + if (type == aTask.type) { + updatedProperties.insert(aTask.updatedProperties.begin(), aTask.updatedProperties.end()); + return; + } + + // Replace the task + type = aTask.type; + updatedProperties = aTask.updatedProperties; + } + }; + + typedef map TaskMap; + + void addItem(const T& aItem) { + WLock l(cs); + queueTask(aItem, MergeTask(ADD_ITEM)); + } + + void removeItem(const T& aItem) { + WLock l(cs); + queueTask(aItem, MergeTask(REMOVE_ITEM)); + } + + void updateItem(const T& aItem, const PropertyIdSet& aUpdatedProperties) { + WLock l(cs); + updatedProperties.insert(aUpdatedProperties.begin(), aUpdatedProperties.end()); + queueTask(aItem, MergeTask(UPDATE_ITEM, aUpdatedProperties)); + } + + void clear() { + WLock l(cs); + updatedProperties.clear(); + tasks.clear(); + } + + void get(typename ItemTasks::TaskMap& tasks_, PropertyIdSet& updatedProperties_) { + WLock l(cs); + tasks_.swap(tasks); + updatedProperties_.swap(updatedProperties); + } +private: + void queueTask(const T& aItem, MergeTask&& aData) { + auto j = tasks.find(aItem); + if (j != tasks.end()) { + (*j).second.merge(aData); + return; + } + + tasks.emplace(aItem, move(aData)); + } + + PropertyIdSet updatedProperties; + SharedMutex cs; + TaskMap tasks; +}; + +} + +#endif diff --git a/web-server/Access.h b/web-server/Access.h index 227ae870..157b9a9d 100644 --- a/web-server/Access.h +++ b/web-server/Access.h @@ -30,6 +30,7 @@ namespace webserver { SEARCH, DOWNLOAD, EVENTS, + TRANSFERS, QUEUE_VIEW, QUEUE_EDIT, diff --git a/web-server/Session.cpp b/web-server/Session.cpp index e877c09e..5c4859ac 100644 --- a/web-server/Session.cpp +++ b/web-server/Session.cpp @@ -106,6 +106,11 @@ namespace webserver { auto oldSocket = getServer()->getSocket(id); if (oldSocket) { oldSocket->debugMessage("Replace session socket"); + + // This must be called before the new socket is associated with this session + fire(SessionListener::SocketConnected(), oldSocket); + oldSocket->setSession(nullptr); + oldSocket->close(websocketpp::close::status::policy_violation, "Another socket was connected to this session"); } diff --git a/web-server/WebServerManager.cpp b/web-server/WebServerManager.cpp index 8014f641..b00f4870 100644 --- a/web-server/WebServerManager.cpp +++ b/web-server/WebServerManager.cpp @@ -41,6 +41,7 @@ namespace webserver { using namespace dcpp; WebServerManager::WebServerManager() : serverThreads(DEFAULT_THREADS), has_io_service(false), ios(2) { userManager = unique_ptr(new WebUserManager(this)); + ios.stop(); //Prevent io service from running until we load } WebServerManager::~WebServerManager() { @@ -289,7 +290,8 @@ namespace webserver { } void WebServerManager::stop() { - socketTimer->stop(true); + if(socketTimer) + socketTimer->stop(true); fire(WebServerManagerListener::Stopping()); if(endpoint_plain.is_listening()) diff --git a/web-server/WebUser.cpp b/web-server/WebUser.cpp index af4ffa39..6fd48f54 100644 --- a/web-server/WebUser.cpp +++ b/web-server/WebUser.cpp @@ -31,6 +31,7 @@ namespace webserver { "search", "download", "events", + "transfers", "queue_view", "queue_edit", diff --git a/web-server/WebUserManager.cpp b/web-server/WebUserManager.cpp index 823a8953..201562da 100644 --- a/web-server/WebUserManager.cpp +++ b/web-server/WebUserManager.cpp @@ -61,7 +61,7 @@ namespace webserver { fire(WebUserManagerListener::UserUpdated(), u); auto uuid = boost::uuids::random_generator()(); - auto session = make_shared(u, boost::uuids::to_string(uuid), aIsSecure, server, aMaxInactivityMinutes, aUserSession); + auto session = std::make_shared(u, boost::uuids::to_string(uuid), aIsSecure, server, aMaxInactivityMinutes, aUserSession); { WLock l(cs); @@ -164,7 +164,7 @@ namespace webserver { const auto& permissions = xml_.getChildAttrib("Permissions"); // Set as admin mainly for compatibility with old accounts if no permissions were found - auto user = make_shared(username, password, permissions.empty()); + auto user = std::make_shared(username, password, permissions.empty()); user->setLastLogin(xml_.getIntChildAttrib("LastLogin")); if (!permissions.empty()) { diff --git a/webapi.vcxproj b/webapi.vcxproj index a3e86301..395eb4db 100644 --- a/webapi.vcxproj +++ b/webapi.vcxproj @@ -177,6 +177,7 @@ + @@ -210,6 +211,8 @@ + + @@ -269,6 +272,7 @@ + diff --git a/webapi.vcxproj.filters b/webapi.vcxproj.filters index ac5539ae..226a0b9d 100644 --- a/webapi.vcxproj.filters +++ b/webapi.vcxproj.filters @@ -228,6 +228,15 @@ Header Files\api + + Header Files\api + + + Header Files\api + + + Header Files\api\common + @@ -380,5 +389,8 @@ Source Files\api + + Source Files\api + \ No newline at end of file