Add resume option to acsmdownloader
This commit is contained in:
parent
5d3112bc38
commit
2f2e4e193e
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
|
Loading…
Reference in New Issue
Block a user