PageRenderTime 60ms CodeModel.GetById 34ms RepoModel.GetById 1ms app.codeStats 0ms

/src/libs/HttpService/HTTPClient/UploadQueue.cpp

https://github.com/epitech-labfree/FireBreath
C++ | 380 lines | 270 code | 60 blank | 50 comment | 69 complexity | 90fb609d33c556401700d5be05432657 MD5 | raw file
  1. /**********************************************************\
  2. Original Author: Dan Weatherford
  3. Imported into FireBreath: Oct 4, 2010
  4. License: Dual license model; choose one of two:
  5. New BSD License
  6. http://www.opensource.org/licenses/bsd-license.php
  7. - or -
  8. GNU Lesser General Public License, version 2.1
  9. http://www.gnu.org/licenses/lgpl-2.1.html
  10. Copyright 2010 Dan Weatherford and Facebook, Inc
  11. \**********************************************************/
  12. #include "UploadQueue.h"
  13. #include "HTTPRequest.h"
  14. #include <boost/algorithm/string.hpp>
  15. #include "../HTTPCommon/Status.h"
  16. #include "../HTTPCommon/Utils.h"
  17. #include "logging.h"
  18. using namespace HTTP;
  19. using namespace boost::algorithm;
  20. UploadQueue::UploadQueue( const std::string& _name )
  21. : name(_name), status(UPLOAD_IDLE), current_queue_bytes(0), current_batch_bytes(0), total_queue_bytes(0),
  22. total_queue_files(0), files_waiting(0), current_upload_request(NULL), current_batch_retry(0),
  23. batch_size(8), max_retries(3)
  24. {
  25. }
  26. UploadQueue::~UploadQueue()
  27. {
  28. }
  29. FB::VariantMap UploadQueue::getEmptyProgressDict() {
  30. FB::VariantMap d;
  31. d["queue_name"] = std::string();
  32. d["current_files"] = FB::VariantList();
  33. d["current_queue_bytes_remaining"] = 0;
  34. d["current_queue_files_remaining"] = 0;
  35. d["total_queue_bytes"] = 0;
  36. d["total_queue_files"] = 0;
  37. d["batches_remaining"] = 0;
  38. return d;
  39. }
  40. FB::VariantMap UploadQueue::getStatusDict(bool include_filenames) {
  41. FB::VariantMap d;
  42. // whole-queue stats
  43. d["queue_name"] = name;
  44. d["current_queue_bytes_remaining"] = current_queue_bytes;
  45. d["total_queue_bytes"] = total_queue_bytes;
  46. d["total_queue_files"] = total_queue_files;
  47. d["current_queue_files_remaining"] = files_waiting;
  48. d["batches_remaining"] = ceil(static_cast<float>(files_waiting) / static_cast<float>(batch_size));
  49. // this-batch stats
  50. if (include_filenames) {
  51. FB::VariantList cf;
  52. for (std::set<std::wstring>::const_iterator it = current_upload_files.begin();
  53. it != current_upload_files.end(); ++it) {
  54. cf.push_back(*it);
  55. }
  56. d["current_files"] = cf;
  57. }
  58. d["current_batch_bytes_total"] = current_batch_bytes;
  59. double current_batch_pct_complete = 0.0;
  60. if (current_upload_request) {
  61. HTTP::Status st = current_upload_request->getStatus();
  62. FB::VariantMap status(st.asDict());
  63. d.insert(status.begin(), status.end());
  64. if (st.send_total) {
  65. current_batch_pct_complete = static_cast<double>(st.bytes_sent)
  66. / static_cast<double>(st.send_total);
  67. }
  68. }
  69. d["current_batch_pct_complete"] = current_batch_pct_complete;
  70. d["overall_progress_pct"] = static_cast<double>(
  71. (total_queue_bytes - (current_queue_bytes + current_batch_bytes))
  72. + (current_batch_pct_complete * current_batch_bytes))
  73. / static_cast<double>(total_queue_bytes);
  74. return d;
  75. }
  76. void UploadQueue::dispatch() {
  77. status = UploadQueue::UPLOAD_IN_PROGRESS;
  78. start_next_upload();
  79. // Deliver the first status dict to HTTP server. We need it to be there before this
  80. // function returns, because otherwise there's a race between the HTTP request posting
  81. // actual progress data and the JS setting up the polling for it, where JS could see
  82. // an empty progress dict and think the uploads are all done.
  83. // TODOTODO
  84. //{
  85. // boost::shared_ptr<UploadStatusHandler> ph = http_status_handler_weak.lock();
  86. // if (ph) ph->postUpdate(getStatusDict());
  87. //}
  88. sendUpdateEvent();
  89. }
  90. void UploadQueue::start_next_upload() {
  91. unsigned int files_started = 0;
  92. boost::shared_ptr<HTTPRequestData> data(new HTTPRequestData);
  93. data->method = "POST";
  94. data->cookies = cookies;
  95. current_upload_files.clear();
  96. current_batch_bytes = 0;
  97. current_batch_retry = 0;
  98. for (std::list<UploadQueueEntry>::iterator it = queue.begin(); it != queue.end(); ++it) {
  99. if (it->status != UploadQueueEntry::ENTRY_WAITING) continue;
  100. try {
  101. UploadQueueEntry& qe = *it;
  102. // Open and process the image.
  103. // We do this now so we can catch errors and attribute them to individual images;
  104. // letting the HTTPRequest do it would fail the whole request if a single image
  105. // was unreadable.
  106. qe.datablock->resolve();
  107. // Pull in post params
  108. for (std::map<std::string, std::string>::iterator pvit = post_vars.begin();
  109. pvit != post_vars.end(); ++pvit) {
  110. qe.target.query_data[pvit->first] = pvit->second;
  111. }
  112. if (files_started == 0) {
  113. // First match sets the target uri
  114. data->uri = qe.target;
  115. } else {
  116. // Subsequently, we can only batch up more uploads to the same endpoint
  117. if (!(data->uri == qe.target)) continue;
  118. }
  119. it->setStatus(UploadQueueEntry::ENTRY_IN_PROGRESS);
  120. --files_waiting;
  121. current_queue_bytes -= qe.filesize;
  122. std::string s = "file" + files_started;
  123. qe.post_field = s;
  124. data->addFile(s, FB::wstring_to_utf8(qe.filename), "application/octet-stream", qe.datablock);
  125. current_upload_files.insert(qe.source_path);
  126. current_batch_bytes += qe.filesize;
  127. ++files_started;
  128. if (files_started >= batch_size) break;
  129. } catch (const std::exception& e) {
  130. it->result = e.what();
  131. it->setStatus(UploadQueueEntry::ENTRY_ERROR);
  132. }
  133. }
  134. if (files_started) {
  135. // We had enough images left in the queue for another request, so kick that off now.
  136. current_upload_request = HTTPRequest::create();
  137. current_upload_request->onStatusChanged(
  138. bind(&UploadQueue::upload_request_status_changed, this, _1)
  139. );
  140. current_upload_request->startRequest(data);
  141. // As long as we're doing uploads, we want to keep the HTTP server
  142. // up to provide progress to the chat bar widget -- so enable deferred shutdown.
  143. // TODOTODO
  144. //try {
  145. // boost::shared_ptr<HTTPService> h = http_srv_inst_weak.lock();
  146. // if (h) h->setDeferShutdown(true);
  147. //} catch (...) {}
  148. } else {
  149. #ifndef NDEBUG
  150. FBLOG_DEBUG("UploadQueue",
  151. "start_next_upload() found no waiting files, running completion handlers\n");
  152. #endif
  153. // All done, post upload finished callback to all instances
  154. FB::VariantMap d = getEmptyProgressDict();
  155. FB::VariantMap failures;
  156. for (std::list<UploadQueueEntry>::iterator it = queue.begin(); it != queue.end(); ++it) {
  157. if (it->status == UploadQueueEntry::ENTRY_ERROR) {
  158. failures[FB::wstring_to_utf8(it->source_path)] = it->result;
  159. #ifndef NDEBUG
  160. FBLOG_WARN("UploadQueue", "Reporting file \"" << it->source_path.c_str()
  161. << "\" as failed: " << it->result.c_str() << std::endl);
  162. #endif
  163. }
  164. }
  165. queue.clear();
  166. if (! failures.empty()) d["failed_files"] = failures;
  167. // TODOTODO
  168. {
  169. StatusUpdateEvent evt(d);
  170. SendEvent(&evt);
  171. }
  172. status = UploadQueue::UPLOAD_COMPLETE;
  173. // fire completion handlers, if available
  174. for (std::list<FB::URI>::iterator it = completion_handlers.begin();
  175. it != completion_handlers.end(); ++it) {
  176. boost::shared_ptr<HTTPRequestData> reqdata(new HTTPRequestData(*it));
  177. reqdata->cookies = cookies;
  178. HTTPRequest::asyncStartRequest(reqdata);
  179. }
  180. if (queue_finished_callback) queue_finished_callback(shared_ptr());
  181. }
  182. }
  183. void UploadQueue::upload_request_status_changed(const HTTP::Status &status) {
  184. // Deliver status dict to interested observers
  185. sendUpdateEvent();
  186. uint64_t this_batch_size = 0;
  187. if (status.state == HTTP::Status::HTTP_ERROR) {
  188. if ((++current_batch_retry) < max_retries) {
  189. // Retry on networking error.
  190. #ifndef NDEBUG
  191. FBLOG_WARN("UploadQueue", "Retrying current batch on networking error ("
  192. << status.last_error.c_str() << ")" << std::endl);
  193. #endif
  194. // Retake ownership of the request data, dispose this request, and start a new one.
  195. // The request data doesn't get mangled by the HTTPRequest object, it should be safe
  196. // to reuse.
  197. boost::shared_ptr<HTTPRequestData> req_data = current_upload_request->getRequest();
  198. current_upload_request->threadSafeDestroy();
  199. current_upload_request = HTTPRequest::create();
  200. current_upload_request->onStatusChanged(
  201. bind(&UploadQueue::upload_request_status_changed, this, _1)
  202. );
  203. current_upload_request->startRequest(req_data);
  204. return;
  205. }
  206. for (std::list<UploadQueueEntry>::iterator it = queue.begin(); it != queue.end(); ++it) {
  207. if (it->status == UploadQueueEntry::ENTRY_IN_PROGRESS) {
  208. this_batch_size += it->filesize;
  209. it->onFailure(status.last_error);
  210. }
  211. }
  212. } else if (status.state == HTTP::Status::COMPLETE) {
  213. HTTPDatablock* db = current_upload_request->getResponse()->coalesceBlocks();
  214. std::multimap<std::string, std::string> file_statuses;
  215. try {
  216. std::vector<std::string> status_lines;
  217. std::string db_str(db->data(), db->size());
  218. std::istringstream data_stream(db_str);
  219. while (!data_stream.eof()) {
  220. std::string tmp;
  221. std::getline(data_stream, tmp);
  222. trim(tmp);
  223. if (tmp.size() && tmp.at(tmp.size() - 1) == '\r') tmp.resize(tmp.size() - 1);
  224. if (tmp.empty()) break;
  225. status_lines.push_back(tmp);
  226. }
  227. file_statuses = parse_http_headers(status_lines.begin(), status_lines.end());
  228. } catch (const std::exception& e) {
  229. #ifndef NDEBUG
  230. printf("Parse error while reading file statuses back from endpoint: %s\n", e.what());
  231. #endif
  232. }
  233. for (std::list<UploadQueueEntry>::iterator it = queue.begin(); it != queue.end(); ++it) {
  234. if (it->status == UploadQueueEntry::ENTRY_IN_PROGRESS) {
  235. this_batch_size += it->filesize;
  236. bool had_error = false;
  237. // See if we got an error condition back from the endpoint
  238. std::multimap<std::string, std::string>::iterator stat_it
  239. = file_statuses.find(it->post_field);
  240. if (stat_it != file_statuses.end()) {
  241. std::string lcase_status = to_lower_copy(stat_it->second);
  242. if (lcase_status.size() >= 5 && (lcase_status.substr(0,5) == "error")) {
  243. #ifndef NDEBUG
  244. FBLOG_WARN("UploadQueue", "Upload endpoint signaled an error for field "
  245. << it->post_field.c_str() << ":" << stat_it->second.c_str() << std::endl);
  246. #endif
  247. it->onFailure(stat_it->second);
  248. had_error = true;
  249. }
  250. } else {
  251. #ifndef NDEBUG
  252. FBLOG_WARN("UploadQueue", "Upload endpoint didn't return a status for field "
  253. << it->post_field.c_str() << std::endl);
  254. #endif
  255. }
  256. if (!had_error) it->setStatus(UploadQueueEntry::ENTRY_COMPLETE);
  257. }
  258. }
  259. } else return;
  260. boost::shared_ptr<HTTPResponseData> response = current_upload_request->getResponse();
  261. if (response) {
  262. for (std::map<std::string, std::string>::iterator it = response->cookies.begin();
  263. it != response->cookies.end(); ++it) {
  264. cookies[it->first] = it->second;
  265. }
  266. }
  267. // using threadSafeDestroy since our caller is probably the i/o service of that request...
  268. current_upload_request->threadSafeDestroy();
  269. current_upload_request = NULL;
  270. // Since this is done (error or not), find another queue entry that's WAITING and start that.
  271. start_next_upload();
  272. }
  273. void UploadQueue::cancel() {
  274. if (current_upload_request) {
  275. HTTPRequest* r = current_upload_request;
  276. current_upload_request = NULL;
  277. r->cancel();
  278. delete r;
  279. }
  280. queue.clear();
  281. status = UploadQueue::UPLOAD_COMPLETE;
  282. if (queue_finished_callback) queue_finished_callback(shared_ptr());
  283. }
  284. void UploadQueue::sendUpdateEvent()
  285. {
  286. StatusUpdateEvent evt(getStatusDict());
  287. SendEvent(&evt);
  288. }
  289. void HTTP::UploadQueue::addCompletionHandler( const FB::URI& uri )
  290. {
  291. if (status != UPLOAD_IDLE) throw std::runtime_error("UploadQueue::addCompletionHandler(): queue has been dispatched, can't be modified");
  292. completion_handlers.push_back(uri);
  293. }
  294. void HTTP::UploadQueue::addFile( const UploadQueueEntry& qe )
  295. {
  296. if (status != UPLOAD_IDLE) throw std::runtime_error("UploadQueue::addFile(): queue has been dispatched, can't be modified");
  297. current_queue_bytes += qe.filesize;
  298. total_queue_bytes += qe.filesize;
  299. ++total_queue_files;
  300. ++files_waiting;
  301. queue.push_back(qe);
  302. }
  303. bool HTTP::UploadQueue::removeFile( const std::wstring& source_path )
  304. {
  305. if (status != UPLOAD_IDLE) throw std::runtime_error("UploadQueue::addFile(): queue has been dispatched, can't be modified");
  306. for (std::list<UploadQueueEntry>::iterator it = queue.begin(); it != queue.end(); ++it) {
  307. if (it->source_path == source_path) {
  308. current_queue_bytes -= it->filesize;
  309. total_queue_bytes -= it->filesize;
  310. --total_queue_files;
  311. --files_waiting;
  312. queue.erase(it);
  313. return true;
  314. }
  315. }
  316. return false; // not found
  317. }
  318. void sendUpdateEvent();