From 2f2e4e193e241d82c929a548d02b2da5fbe4dd8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soutad=C3=A9?= Date: Wed, 23 Mar 2022 21:05:56 +0100 Subject: [PATCH] Add resume option to acsmdownloader --- include/drmprocessorclient.h | 3 ++- include/libgourou.h | 6 ++++-- include/libgourou_common.h | 12 +++++++++--- src/libgourou.cpp | 10 +++++----- utils/acsmdownloader.cpp | 12 +++++++++--- utils/drmprocessorclientimpl.cpp | 20 ++++++++++++++++---- utils/drmprocessorclientimpl.h | 2 +- 7 files changed, 46 insertions(+), 19 deletions(-) diff --git a/include/drmprocessorclient.h b/include/drmprocessorclient.h index 7a28988..6b02b81 100644 --- a/include/drmprocessorclient.h +++ b/include/drmprocessorclient.h @@ -99,10 +99,11 @@ namespace gourou * @param contentType Optional content type of POST Data * @param responseHeaders Optional Response headers of HTTP request * @param fd Optional file descriptor to write request result + * @param resume false if target file should be truncated, true to try resume download (works only in combination with a valid fd) * * @return data of HTTP response */ - virtual std::string sendHTTPRequest(const std::string& URL, const std::string& POSTData=std::string(""), const std::string& contentType=std::string(""), std::map* responseHeaders=0, int fd=0) = 0; + virtual std::string sendHTTPRequest(const std::string& URL, const std::string& POSTData=std::string(""), const std::string& contentType=std::string(""), std::map* responseHeaders=0, int fd=0, bool resume=false) = 0; }; class RSAInterface diff --git a/include/libgourou.h b/include/libgourou.h index 8146814..abf72d5 100644 --- a/include/libgourou.h +++ b/include/libgourou.h @@ -81,10 +81,11 @@ namespace gourou * * @param item Item from fulfill() method * @param path Output file path + * @param resume false if target file should be truncated, true to try resume download * * @return Type of downloaded item */ - ITEM_TYPE download(FulfillmentItem* item, std::string path); + ITEM_TYPE download(FulfillmentItem* item, std::string path, bool resume=false); /** * @brief SignIn into ACS Server (required to activate device) @@ -135,10 +136,11 @@ namespace gourou * @param contentType Optional content type of POST Data * @param responseHeaders Optional Response headers of HTTP request * @param fd Optional File descriptor to write received data + * @param resume false if target file should be truncated, true to try resume download (works only in combination of a valid fd) * * @return data of HTTP response */ - ByteArray sendRequest(const std::string& URL, const std::string& POSTData=std::string(), const char* contentType=0, std::map* responseHeaders=0, int fd=0); + ByteArray sendRequest(const std::string& URL, const std::string& POSTData=std::string(), const char* contentType=0, std::map* responseHeaders=0, int fd=0, bool resume=false); /** * @brief Send HTTP POST request to URL with document as POSTData diff --git a/include/libgourou_common.h b/include/libgourou_common.h index f640a9f..298ec1f 100644 --- a/include/libgourou_common.h +++ b/include/libgourou_common.h @@ -287,13 +287,19 @@ namespace gourou } /** - * @brief Open a file descriptor on path. If it already exists, it's truncated + * @brief Open a file descriptor on path. If it already exists and truncate == true, it's truncated * * @return Created fd, must be closed */ - static inline int createNewFile(std::string path) + static inline int createNewFile(std::string path, bool truncate=true) { - int fd = open(path.c_str(), O_CREAT|O_WRONLY|O_TRUNC, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH); + int options = O_CREAT|O_WRONLY; + if (truncate) + options |= O_TRUNC; + else + options |= O_APPEND; + + int fd = open(path.c_str(), options, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH); if (fd <= 0) EXCEPTION(GOUROU_FILE_ERROR, "Unable to create " << path); diff --git a/src/libgourou.cpp b/src/libgourou.cpp index a8e3625..6ec3e07 100644 --- a/src/libgourou.cpp +++ b/src/libgourou.cpp @@ -300,11 +300,11 @@ namespace gourou appendTextElem(root, "adept:expiration", buffer); } - ByteArray DRMProcessor::sendRequest(const std::string& URL, const std::string& POSTdata, const char* contentType, std::map* responseHeaders, int fd) + ByteArray DRMProcessor::sendRequest(const std::string& URL, const std::string& POSTdata, const char* contentType, std::map* responseHeaders, int fd, bool resume) { if (contentType == 0) contentType = ""; - std::string reply = client->sendHTTPRequest(URL, POSTdata, contentType, responseHeaders, fd); + std::string reply = client->sendHTTPRequest(URL, POSTdata, contentType, responseHeaders, fd, resume); if (fd) return ByteArray(); @@ -583,7 +583,7 @@ namespace gourou return new FulfillmentItem(fulfillReply, user); } - DRMProcessor::ITEM_TYPE DRMProcessor::download(FulfillmentItem* item, std::string path) + DRMProcessor::ITEM_TYPE DRMProcessor::download(FulfillmentItem* item, std::string path, bool resume) { ITEM_TYPE res = EPUB; @@ -592,9 +592,9 @@ namespace gourou std::map headers; - int fd = createNewFile(path); + int fd = createNewFile(path, !resume); - sendRequest(item->getDownloadURL(), "", 0, &headers, fd); + sendRequest(item->getDownloadURL(), "", 0, &headers, fd, resume); close(fd); diff --git a/utils/acsmdownloader.cpp b/utils/acsmdownloader.cpp index aeb31b1..360cfda 100644 --- a/utils/acsmdownloader.cpp +++ b/utils/acsmdownloader.cpp @@ -42,6 +42,7 @@ static const char* acsmFile = 0; static bool exportPrivateKey = false; static const char* outputFile = 0; static const char* outputDir = 0; +static bool resume = false; class ACSMDownloader @@ -104,7 +105,7 @@ public: filename = std::string(outputDir) + "/" + filename; } - gourou::DRMProcessor::ITEM_TYPE type = processor.download(item, filename); + gourou::DRMProcessor::ITEM_TYPE type = processor.download(item, filename, resume); if (!outputFile) { @@ -133,7 +134,7 @@ static void usage(const char* cmd) { std::cout << "Download EPUB file from ACSM request file" << std::endl; - std::cout << "Usage: " << cmd << " [(-d|--device-file) device.xml] [(-a|--activation-file) activation.xml] [(-k|--device-key-file) devicesalt] [(-O|--output-dir) dir] [(-o|--output-file) output(.epub|.pdf|.der)] [(-v|--verbose)] [(-h|--help)] (-f|--acsm-file) file.acsm|(-e|--export-private-key)" << std::endl << std::endl; + std::cout << "Usage: " << cmd << " [(-d|--device-file) device.xml] [(-a|--activation-file) activation.xml] [(-k|--device-key-file) devicesalt] [(-O|--output-dir) dir] [(-o|--output-file) output(.epub|.pdf|.der)] [(-r|--resume)] [(-v|--verbose)] [(-h|--help)] (-f|--acsm-file) file.acsm|(-e|--export-private-key)" << std::endl << std::endl; std::cout << " " << "-d|--device-file" << "\t" << "device.xml file from eReader" << std::endl; std::cout << " " << "-a|--activation-file" << "\t" << "activation.xml file from eReader" << std::endl; @@ -142,6 +143,7 @@ static void usage(const char* cmd) std::cout << " " << "-o|--output-file" << "\t" << "Optional output filename (default )" << std::endl; std::cout << " " << "-f|--acsm-file" << "\t" << "ACSM request file for epub download" << std::endl; std::cout << " " << "-e|--export-private-key"<< "\t" << "Export private key in DER format" << std::endl; + std::cout << " " << "-r|--resume" << "\t\t" << "Try to resume download (in case of previous failure)" << std::endl; std::cout << " " << "-v|--verbose" << "\t\t" << "Increase verbosity, can be set multiple times" << std::endl; std::cout << " " << "-V|--version" << "\t\t" << "Display libgourou version" << std::endl; std::cout << " " << "-h|--help" << "\t\t" << "This help" << std::endl; @@ -171,13 +173,14 @@ int main(int argc, char** argv) {"output-file", required_argument, 0, 'o' }, {"acsm-file", required_argument, 0, 'f' }, {"export-private-key",no_argument, 0, 'e' }, + {"resume", no_argument, 0, 'r' }, {"verbose", no_argument, 0, 'v' }, {"version", no_argument, 0, 'V' }, {"help", no_argument, 0, 'h' }, {0, 0, 0, 0 } }; - c = getopt_long(argc, argv, "d:a:k:O:o:f:evVh", + c = getopt_long(argc, argv, "d:a:k:O:o:f:ervVh", long_options, &option_index); if (c == -1) break; @@ -204,6 +207,9 @@ int main(int argc, char** argv) case 'e': exportPrivateKey = true; break; + case 'r': + resume = true; + break; case 'v': verbose++; break; diff --git a/utils/drmprocessorclientimpl.cpp b/utils/drmprocessorclientimpl.cpp index c9ebaad..795d3c9 100644 --- a/utils/drmprocessorclientimpl.cpp +++ b/utils/drmprocessorclientimpl.cpp @@ -175,7 +175,7 @@ static size_t curlHeaders(char *buffer, size_t size, size_t nitems, void *userda return size*nitems; } -std::string DRMProcessorClientImpl::sendHTTPRequest(const std::string& URL, const std::string& POSTData, const std::string& contentType, std::map* responseHeaders, int fd) +std::string DRMProcessorClientImpl::sendHTTPRequest(const std::string& URL, const std::string& POSTData, const std::string& contentType, std::map* responseHeaders, int fd, bool resume) { gourou::ByteArray replyData; std::map localHeaders; @@ -191,6 +191,18 @@ std::string DRMProcessorClientImpl::sendHTTPRequest(const std::string& URL, cons unsigned prevDownloadedBytes; downloadedBytes = 0; + if (fd && resume) + { + struct stat _stat; + if (!fstat(fd, &_stat)) + { + GOUROU_LOG(gourou::WARN, "Resume download @ " << _stat.st_size << " bytes"); + downloadedBytes = _stat.st_size; + } + else + GOUROU_LOG(gourou::WARN, "Want to resume, but fstat failed"); + } + CURL *curl = curl_easy_init(); CURLcode res; curl_easy_setopt(curl, CURLOPT_URL, URL.c_str()); @@ -244,7 +256,7 @@ std::string DRMProcessorClientImpl::sendHTTPRequest(const std::string& URL, cons // Connexion failed, wait & retry if (res == CURLE_COULDNT_CONNECT) { - GOUROU_LOG(gourou::WARN, "Connection failed, attempt " << (i+1) << "/" << HTTP_REQ_MAX_RETRY); + GOUROU_LOG(gourou::WARN, "\nConnection failed, attempt " << (i+1) << "/" << HTTP_REQ_MAX_RETRY); } // Transfer failed but some data has been received // --> try again without incrementing tries @@ -252,11 +264,11 @@ std::string DRMProcessorClientImpl::sendHTTPRequest(const std::string& URL, cons { if (prevDownloadedBytes != downloadedBytes) { - GOUROU_LOG(gourou::WARN, "Connection broken, but data received, try again"); + GOUROU_LOG(gourou::WARN, "\nConnection broken, but data received, try again"); i--; } else - GOUROU_LOG(gourou::WARN, "Connection broken and no data received, attempt " << (i+1) << "/" << HTTP_REQ_MAX_RETRY); + GOUROU_LOG(gourou::WARN, "\nConnection broken and no data received, attempt " << (i+1) << "/" << HTTP_REQ_MAX_RETRY); } // Other error --> fail else diff --git a/utils/drmprocessorclientimpl.h b/utils/drmprocessorclientimpl.h index d368f00..53a4a03 100644 --- a/utils/drmprocessorclientimpl.h +++ b/utils/drmprocessorclientimpl.h @@ -46,7 +46,7 @@ public: virtual void randBytes(unsigned char* bytesOut, unsigned int length); /* HTTP interface */ - virtual std::string sendHTTPRequest(const std::string& URL, const std::string& POSTData=std::string(""), const std::string& contentType=std::string(""), std::map* responseHeaders=0, int fd=0); + virtual std::string sendHTTPRequest(const std::string& URL, const std::string& POSTData=std::string(""), const std::string& contentType=std::string(""), std::map* responseHeaders=0, int fd=0, bool resume=false); virtual void RSAPrivateEncrypt(const unsigned char* RSAKey, unsigned int RSAKeyLength, const RSA_KEY_TYPE keyType, const std::string& password,