diff --git a/include/downloader.h b/include/downloader.h index c1dd5ef96..13e1a229b 100644 --- a/include/downloader.h +++ b/include/downloader.h @@ -21,6 +21,8 @@ #define KIWIX_DOWNLOADER_H #include +#include +#include #include #include @@ -40,6 +42,41 @@ class AriaError : public std::runtime_error { AriaError(const std::string& message) : std::runtime_error(message) {} }; + +class Download { + public: + typedef enum { ACTIVE, WAITING, PAUSED, ERROR, COMPLETE, REMOVED, UNKNOWN } StatusResult; + + Download() : + m_status(UNKNOWN) {} + Download(std::shared_ptr p_aria, std::string did) + : mp_aria(p_aria), + m_status(UNKNOWN), + m_did(did) {}; + void updateStatus(bool follow=false); + StatusResult getStatus() { return m_status; } + std::string getDid() { return m_did; } + std::string getFollowedBy() { return m_followedBy; } + uint64_t getTotalLength() { return m_totalLength; } + uint64_t getCompletedLength() { return m_completedLength; } + uint64_t getDownloadSpeed() { return m_downloadSpeed; } + uint64_t getVerifiedLength() { return m_verifiedLength; } + std::string getPath() { return m_path; } + std::vector& getUris() { return m_uris; } + + protected: + std::shared_ptr mp_aria; + StatusResult m_status; + std::string m_did = ""; + std::string m_followedBy = ""; + uint64_t m_totalLength; + uint64_t m_completedLength; + uint64_t m_downloadSpeed; + uint64_t m_verifiedLength; + std::vector m_uris; + std::string m_path; +}; + /** * A tool to download things. * @@ -60,9 +97,15 @@ class Downloader */ DownloadedFile download(const std::string& url); - private: + Download* startDownload(const std::string& uri); + Download* getDownload(const std::string& did); - std::unique_ptr mp_aria; + size_t getNbDownload() { return m_knownDownloads.size(); } + std::vector getDownloadIds(); + + private: + std::map> m_knownDownloads; + std::shared_ptr mp_aria; }; } diff --git a/src/downloader.cpp b/src/downloader.cpp index 20112e71a..9db8dc2ca 100644 --- a/src/downloader.cpp +++ b/src/downloader.cpp @@ -20,6 +20,7 @@ #include "downloader.h" #include "common/pathTools.h" +#include #include #include @@ -33,10 +34,73 @@ namespace kiwix { +void Download::updateStatus(bool follow) +{ + static std::vector statusKey = {"status", "files", "totalLength", + "completedLength", "followedBy", + "downloadSpeed", "verifiedLength"}; + std::string strStatus; + if(follow && !m_followedBy.empty()) { + strStatus = mp_aria->tellStatus(m_followedBy, statusKey); + } else { + strStatus = mp_aria->tellStatus(m_did, statusKey); + } +// std::cout << strStatus << std::endl; + MethodResponse response(strStatus); + if (response.isFault()) { + m_status = Download::UNKNOWN; + return; + } + auto structNode = response.getParams().getParam(0).getValue().getStruct(); + auto _status = structNode.getMember("status").getValue().getAsS(); + auto status = _status == "active" ? Download::ACTIVE + : _status == "waiting" ? Download::WAITING + : _status == "paused" ? Download::PAUSED + : _status == "error" ? Download::ERROR + : _status == "complete" ? Download::COMPLETE + : _status == "removed" ? Download::REMOVED + : Download::UNKNOWN; + if (status == COMPLETE) { + try { + auto followedByMember = structNode.getMember("followedBy"); + m_followedBy = followedByMember.getValue().getArray().getValue(0).getAsS(); + if (follow) { + status = ACTIVE; + updateStatus(true); + return; + } + } catch (InvalidRPCNode& e) { } + } + m_status = status; + m_totalLength = std::stoull(structNode.getMember("totalLength").getValue().getAsS()); + m_completedLength = std::stoull(structNode.getMember("completedLength").getValue().getAsS()); + m_downloadSpeed = std::stoull(structNode.getMember("downloadSpeed").getValue().getAsS()); + try { + auto verifiedLengthValue = structNode.getMember("verifiedLength").getValue(); + m_verifiedLength = std::stoull(verifiedLengthValue.getAsS()); + } catch (InvalidRPCNode& e) { m_verifiedLength = 0; } + auto filesMember = structNode.getMember("files"); + auto fileStruct = filesMember.getValue().getArray().getValue(0).getStruct(); + m_path = fileStruct.getMember("path").getValue().getAsS(); + auto urisArray = fileStruct.getMember("uris").getValue().getArray(); + int index = 0; + m_uris.clear(); + while(true) { + try { + auto uriNode = urisArray.getValue(index++).getStruct().getMember("uri"); + m_uris.push_back(uriNode.getValue().getAsS()); + } catch(InvalidRPCNode& e) { break; } + } +} + /* Constructor */ Downloader::Downloader() : mp_aria(new Aria2()) { + for (auto gid : mp_aria->tellActive()) { + m_knownDownloads[gid] = std::unique_ptr(new Download(mp_aria, gid)); + m_knownDownloads[gid]->updateStatus(); + } } @@ -50,39 +114,29 @@ void Downloader::close() mp_aria->close(); } -pugi::xml_node find_member_in_struct(pugi::xml_node struct_node, std::string member_name) { - for(auto member=struct_node.first_child(); member; member=member.next_sibling()) { - std::string _member_name = member.child("name").text().get(); - if (_member_name == member_name) { - return member.child("value"); - } +std::vector Downloader::getDownloadIds() { + std::vector ret; + for(auto& p:m_knownDownloads) { + ret.push_back(p.first); } - return pugi::xml_node(); + return ret; } DownloadedFile Downloader::download(const std::string& url) { DownloadedFile fileHandle; try { - std::vector uris = {url}; std::vector status_key = {"status", "files"}; - std::string gid; - - gid = mp_aria->addUri(uris); - std::cerr << "gid is : " << gid << std::endl; + auto download = startDownload(url); + std::cerr << "gid is : " << download->getDid() << std::endl; pugi::xml_document ret; while(true) { - auto strStatus = mp_aria->tellStatus(gid, status_key); - MethodResponse response(strStatus); - auto structNode = response.getParams().getParam(0).getValue().getStruct(); - auto status = structNode.getMember("status").getValue().getAsS(); - std::cerr << "Status is " << status << std::endl; - if (status == "complete") { + download->updateStatus(); + std::cerr << "Status is " << download->getStatus() << std::endl; + if (download->getStatus() == Download::COMPLETE) { fileHandle.success = true; - auto filesMember = structNode.getMember("files"); - auto fileStruct = filesMember.getValue().getArray().getValue(0).getStruct(); - fileHandle.path = fileStruct.getMember("path").getValue().getAsS(); + fileHandle.path = download->getPath(); std::cerr << "FilePath is " << fileHandle.path << std::endl; - } else if (status == "error") { + } else if (download->getStatus() == Download::ERROR) { fileHandle.success = false; } else { // [TODO] Be wise here. @@ -95,4 +149,33 @@ DownloadedFile Downloader::download(const std::string& url) { return fileHandle; } +Download* Downloader::startDownload(const std::string& uri) +{ + for (auto& p: m_knownDownloads) { + auto& d = p.second; + auto& uris = d->getUris(); + if (std::find(uris.begin(), uris.end(), uri) != uris.end()) + return d.get(); + } + std::vector uris = {uri}; + auto gid = mp_aria->addUri(uris); + m_knownDownloads[gid] = std::unique_ptr(new Download(mp_aria, gid)); + return m_knownDownloads[gid].get(); +} + +Download* Downloader::getDownload(const std::string& did) +{ + try { + return m_knownDownloads.at(did).get(); + } catch(exception& e) { + for (auto gid : mp_aria->tellActive()) { + if (gid == did) { + m_knownDownloads[gid] = std::unique_ptr(new Download(mp_aria, gid)); + return m_knownDownloads[gid].get(); + } + } + throw e; + } +} + }