Add resume option to acsmdownloader

This commit is contained in:
Grégory Soutadé 2022-03-23 21:05:56 +01:00
parent 5d3112bc38
commit 2f2e4e193e
7 changed files with 46 additions and 19 deletions

View File

@ -99,10 +99,11 @@ namespace gourou
* @param contentType Optional content type of POST Data * @param contentType Optional content type of POST Data
* @param responseHeaders Optional Response headers of HTTP request * @param responseHeaders Optional Response headers of HTTP request
* @param fd Optional file descriptor to write request result * @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 * @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<std::string, std::string>* 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<std::string, std::string>* responseHeaders=0, int fd=0, bool resume=false) = 0;
}; };
class RSAInterface class RSAInterface

View File

@ -81,10 +81,11 @@ namespace gourou
* *
* @param item Item from fulfill() method * @param item Item from fulfill() method
* @param path Output file path * @param path Output file path
* @param resume false if target file should be truncated, true to try resume download
* *
* @return Type of downloaded item * @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) * @brief SignIn into ACS Server (required to activate device)
@ -135,10 +136,11 @@ namespace gourou
* @param contentType Optional content type of POST Data * @param contentType Optional content type of POST Data
* @param responseHeaders Optional Response headers of HTTP request * @param responseHeaders Optional Response headers of HTTP request
* @param fd Optional File descriptor to write received data * @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 * @return data of HTTP response
*/ */
ByteArray sendRequest(const std::string& URL, const std::string& POSTData=std::string(), const char* contentType=0, std::map<std::string, std::string>* responseHeaders=0, int fd=0); ByteArray sendRequest(const std::string& URL, const std::string& POSTData=std::string(), const char* contentType=0, std::map<std::string, std::string>* responseHeaders=0, int fd=0, bool resume=false);
/** /**
* @brief Send HTTP POST request to URL with document as POSTData * @brief Send HTTP POST request to URL with document as POSTData

View File

@ -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 * @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) if (fd <= 0)
EXCEPTION(GOUROU_FILE_ERROR, "Unable to create " << path); EXCEPTION(GOUROU_FILE_ERROR, "Unable to create " << path);

View File

@ -300,11 +300,11 @@ namespace gourou
appendTextElem(root, "adept:expiration", buffer); appendTextElem(root, "adept:expiration", buffer);
} }
ByteArray DRMProcessor::sendRequest(const std::string& URL, const std::string& POSTdata, const char* contentType, std::map<std::string, std::string>* responseHeaders, int fd) ByteArray DRMProcessor::sendRequest(const std::string& URL, const std::string& POSTdata, const char* contentType, std::map<std::string, std::string>* responseHeaders, int fd, bool resume)
{ {
if (contentType == 0) if (contentType == 0)
contentType = ""; 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(); if (fd) return ByteArray();
@ -583,7 +583,7 @@ namespace gourou
return new FulfillmentItem(fulfillReply, user); 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; ITEM_TYPE res = EPUB;
@ -592,9 +592,9 @@ namespace gourou
std::map<std::string, std::string> headers; std::map<std::string, std::string> 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); close(fd);

View File

@ -42,6 +42,7 @@ static const char* acsmFile = 0;
static bool exportPrivateKey = false; static bool exportPrivateKey = false;
static const char* outputFile = 0; static const char* outputFile = 0;
static const char* outputDir = 0; static const char* outputDir = 0;
static bool resume = false;
class ACSMDownloader class ACSMDownloader
@ -104,7 +105,7 @@ public:
filename = std::string(outputDir) + "/" + filename; 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) 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 << "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 << " " << "-d|--device-file" << "\t" << "device.xml file from eReader" << std::endl;
std::cout << " " << "-a|--activation-file" << "\t" << "activation.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 <title.(epub|pdf|der)>)" << std::endl; std::cout << " " << "-o|--output-file" << "\t" << "Optional output filename (default <title.(epub|pdf|der)>)" << std::endl;
std::cout << " " << "-f|--acsm-file" << "\t" << "ACSM request file for epub download" << 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 << " " << "-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|--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 << " " << "-V|--version" << "\t\t" << "Display libgourou version" << std::endl;
std::cout << " " << "-h|--help" << "\t\t" << "This help" << 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' }, {"output-file", required_argument, 0, 'o' },
{"acsm-file", required_argument, 0, 'f' }, {"acsm-file", required_argument, 0, 'f' },
{"export-private-key",no_argument, 0, 'e' }, {"export-private-key",no_argument, 0, 'e' },
{"resume", no_argument, 0, 'r' },
{"verbose", no_argument, 0, 'v' }, {"verbose", no_argument, 0, 'v' },
{"version", no_argument, 0, 'V' }, {"version", no_argument, 0, 'V' },
{"help", no_argument, 0, 'h' }, {"help", no_argument, 0, 'h' },
{0, 0, 0, 0 } {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); long_options, &option_index);
if (c == -1) if (c == -1)
break; break;
@ -204,6 +207,9 @@ int main(int argc, char** argv)
case 'e': case 'e':
exportPrivateKey = true; exportPrivateKey = true;
break; break;
case 'r':
resume = true;
break;
case 'v': case 'v':
verbose++; verbose++;
break; break;

View File

@ -175,7 +175,7 @@ static size_t curlHeaders(char *buffer, size_t size, size_t nitems, void *userda
return size*nitems; return size*nitems;
} }
std::string DRMProcessorClientImpl::sendHTTPRequest(const std::string& URL, const std::string& POSTData, const std::string& contentType, std::map<std::string, std::string>* responseHeaders, int fd) std::string DRMProcessorClientImpl::sendHTTPRequest(const std::string& URL, const std::string& POSTData, const std::string& contentType, std::map<std::string, std::string>* responseHeaders, int fd, bool resume)
{ {
gourou::ByteArray replyData; gourou::ByteArray replyData;
std::map<std::string, std::string> localHeaders; std::map<std::string, std::string> localHeaders;
@ -191,6 +191,18 @@ std::string DRMProcessorClientImpl::sendHTTPRequest(const std::string& URL, cons
unsigned prevDownloadedBytes; unsigned prevDownloadedBytes;
downloadedBytes = 0; 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(); CURL *curl = curl_easy_init();
CURLcode res; CURLcode res;
curl_easy_setopt(curl, CURLOPT_URL, URL.c_str()); 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 // Connexion failed, wait & retry
if (res == CURLE_COULDNT_CONNECT) 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 // Transfer failed but some data has been received
// --> try again without incrementing tries // --> try again without incrementing tries
@ -252,11 +264,11 @@ std::string DRMProcessorClientImpl::sendHTTPRequest(const std::string& URL, cons
{ {
if (prevDownloadedBytes != downloadedBytes) 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--; i--;
} }
else 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 // Other error --> fail
else else

View File

@ -46,7 +46,7 @@ public:
virtual void randBytes(unsigned char* bytesOut, unsigned int length); virtual void randBytes(unsigned char* bytesOut, unsigned int length);
/* HTTP interface */ /* HTTP interface */
virtual std::string sendHTTPRequest(const std::string& URL, const std::string& POSTData=std::string(""), const std::string& contentType=std::string(""), std::map<std::string, std::string>* 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<std::string, std::string>* responseHeaders=0, int fd=0, bool resume=false);
virtual void RSAPrivateEncrypt(const unsigned char* RSAKey, unsigned int RSAKeyLength, virtual void RSAPrivateEncrypt(const unsigned char* RSAKey, unsigned int RSAKeyLength,
const RSA_KEY_TYPE keyType, const std::string& password, const RSA_KEY_TYPE keyType, const std::string& password,