Download.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507
  1. #include <iostream>
  2. #include <stdio.h>
  3. #include <cpp3ds/System/FileSystem.hpp>
  4. #include <cpp3ds/System/I18n.hpp>
  5. #include <cpp3ds/System/Lock.hpp>
  6. #include <cpp3ds/System/Service.hpp>
  7. #include <cpp3ds/System/Sleep.hpp>
  8. #include "Download.hpp"
  9. #include "AssetManager.hpp"
  10. #include "DownloadQueue.hpp"
  11. #include "Theme.hpp"
  12. #include "States/DialogState.hpp"
  13. #include "States/BrowseState.hpp"
  14. namespace FreeShop {
  15. Download::Download(const std::string &url, const std::string &destination)
  16. : m_thread(&Download::run, this)
  17. , m_progress(0.f)
  18. , m_canSendTop(true)
  19. , m_markedForDelete(false)
  20. , m_cancelFlag(false)
  21. , m_status(Queued)
  22. , m_downloadPos(0)
  23. , m_appItem(nullptr)
  24. , m_timesToRetry(3)
  25. , m_timeout(cpp3ds::seconds(10))
  26. , m_bufferSize(16*1024)
  27. , m_isProgressTransitioning(false)
  28. {
  29. setUrl(url);
  30. setDestination(destination);
  31. // m_thread.setStackSize(64*1024);
  32. // m_thread.setAffinity(1);
  33. }
  34. Download::~Download()
  35. {
  36. cancel();
  37. }
  38. bool Download::setUrl(const std::string &url)
  39. {
  40. size_t pos;
  41. pos = url.find("://");
  42. if (pos != std::string::npos) {
  43. pos = url.find("/", pos + 3);
  44. if (pos == std::string::npos) {
  45. m_host = url;
  46. m_uri = "/";
  47. } else {
  48. m_host = url.substr(0, pos);
  49. m_uri = url.substr(pos);
  50. }
  51. }
  52. m_http.setHost(m_host);
  53. m_request.setUri(m_uri);
  54. return true;
  55. }
  56. void Download::start()
  57. {
  58. m_buffer.clear();
  59. m_thread.launch(); // run()
  60. }
  61. void Download::run()
  62. {
  63. if (!m_destination.empty())
  64. {
  65. m_file = fopen(cpp3ds::FileSystem::getFilePath(m_destination).c_str(), "w");
  66. if (!m_file)
  67. return;
  68. }
  69. m_status = Downloading;
  70. m_cancelFlag = false;
  71. bool failed = false;
  72. int retryCount = 0;
  73. m_request.setField("Range", _("bytes=%u-", m_downloadPos));
  74. cpp3ds::Http::RequestCallback dataCallback = [&](const void* data, size_t len, size_t processed, const cpp3ds::Http::Response& response)
  75. {
  76. cpp3ds::Lock lock(m_mutex);
  77. if (!m_destination.empty())
  78. {
  79. if (response.getStatus() == cpp3ds::Http::Response::Ok || response.getStatus() == cpp3ds::Http::Response::PartialContent)
  80. {
  81. const char *bufdata = reinterpret_cast<const char*>(data);
  82. m_buffer.insert(m_buffer.end(), bufdata, bufdata + len);
  83. if (m_buffer.size() > 1024 * 50)
  84. {
  85. fwrite(&m_buffer[0], sizeof(char), m_buffer.size(), m_file);
  86. m_buffer.clear();
  87. }
  88. }
  89. }
  90. if (!m_cancelFlag && m_onData)
  91. failed = !m_onData(data, len, processed, response);
  92. if (getStatus() != Suspended)
  93. m_downloadPos += len;
  94. if (getStatus() == Failed)
  95. failed = true;
  96. return !m_cancelFlag && !failed && getStatus() == Downloading;
  97. };
  98. // Loop in case there are URLs pushed to queue later
  99. while (1)
  100. {
  101. retryCount = 0;
  102. // Retry loop in case of connection failures
  103. do {
  104. // Wait in case of internet loss, but exit when canceled or suspended
  105. while (!cpp3ds::Service::isEnabled(cpp3ds::Httpc) && !m_cancelFlag && getStatus() != Suspended)
  106. {
  107. setProgressMessage(_("Waiting for internet..."));
  108. cpp3ds::sleep(cpp3ds::milliseconds(200));
  109. }
  110. m_response = m_http.sendRequest(m_request, m_timeout, dataCallback, m_bufferSize);
  111. // Follow all redirects
  112. while (m_response.getStatus() == cpp3ds::Http::Response::MovedPermanently || m_response.getStatus() == cpp3ds::Http::Response::MovedTemporarily)
  113. {
  114. setUrl(m_response.getField("Location"));
  115. m_response = m_http.sendRequest(m_request, m_timeout, dataCallback, m_bufferSize);
  116. }
  117. // Retry failed connections (all error codes >= 1000)
  118. if (m_response.getStatus() >= 1000)
  119. {
  120. if (retryCount >= m_timesToRetry)
  121. {
  122. if (m_timesToRetry == 0)
  123. setProgressMessage(_("Failed"));
  124. else
  125. setProgressMessage(_("Retry attempts exceeded"));
  126. failed = true;
  127. break;
  128. }
  129. std::cout << _("Retrying... %d", retryCount).toAnsiString() << std::endl;
  130. setProgressMessage(_("Retrying... %d", ++retryCount));
  131. m_request.setField("Range", _("bytes=%u-", m_downloadPos));
  132. cpp3ds::sleep(cpp3ds::milliseconds(1000));
  133. }
  134. } while (m_response.getStatus() >= 1000 &&
  135. !m_cancelFlag && getStatus() != Suspended);
  136. if (m_response.getStatus() != cpp3ds::Http::Response::Ok && m_response.getStatus() != cpp3ds::Http::Response::PartialContent)
  137. break;
  138. if (m_cancelFlag || failed || getStatus() == Suspended)
  139. break;
  140. // Download next in queue
  141. if (m_urlQueue.size() > 0)
  142. {
  143. auto nextUrl = m_urlQueue.front();
  144. setUrl(nextUrl.first);
  145. m_downloadPos = nextUrl.second;
  146. m_urlQueue.pop();
  147. m_request.setField("Range", _("bytes=%u-", m_downloadPos));
  148. }
  149. else
  150. break;
  151. }
  152. if (m_urlQueue.size() > 0 && getStatus() != Suspended)
  153. std::queue<std::pair<std::string,cpp3ds::Uint64>>().swap(m_urlQueue);
  154. if (getStatus() != Suspended)
  155. {
  156. m_canSendTop = false;
  157. if (m_cancelFlag)
  158. {
  159. m_markedForDelete = true;
  160. m_status = Canceled;
  161. }
  162. else if (failed)
  163. m_status = Failed;
  164. else
  165. m_status = Finished;
  166. }
  167. // Write remaining buffer and close downloaded file
  168. if (!m_destination.empty())
  169. {
  170. if (m_buffer.size() > 0)
  171. fwrite(&m_buffer[0], sizeof(char), m_buffer.size(), m_file);
  172. fclose(m_file);
  173. if (m_status != Finished)
  174. remove(cpp3ds::FileSystem::getFilePath(m_destination).c_str());
  175. }
  176. if (m_onFinish)
  177. m_onFinish(m_cancelFlag, failed);
  178. m_http.close();
  179. }
  180. void Download::cancel(bool wait)
  181. {
  182. m_cancelFlag = true;
  183. if (wait)
  184. m_thread.wait();
  185. }
  186. bool Download::isCanceled()
  187. {
  188. return m_cancelFlag;
  189. }
  190. void Download::setProgress(float progress)
  191. {
  192. m_progress = progress;
  193. //m_progressBar.setSize(cpp3ds::Vector2f(m_progress * m_size.x, m_size.y));
  194. if (!m_isProgressTransitioning) {
  195. m_isProgressTransitioning = true;
  196. TweenEngine::Tween::to(m_progressBar, util3ds::TweenRectangleShape::SIZE, 0.2f)
  197. .target(m_progress * m_size.x, m_size.y)
  198. .setCallback(TweenEngine::TweenCallback::COMPLETE, [=](TweenEngine::BaseTween* source) {
  199. m_isProgressTransitioning = false;
  200. })
  201. .start(m_tweenManager);
  202. }
  203. }
  204. float Download::getProgress() const
  205. {
  206. return m_progress;
  207. }
  208. void Download::setProgressMessage(const std::string &message)
  209. {
  210. m_progressMessage = message;
  211. m_textProgress.setString(message);
  212. }
  213. const std::string &Download::getProgressMessage() const
  214. {
  215. return m_progressMessage;
  216. }
  217. void Download::draw(cpp3ds::RenderTarget &target, cpp3ds::RenderStates states) const
  218. {
  219. states.transform *= getTransform();
  220. target.draw(m_background, states);
  221. if (m_progress > 0.f && m_progress < 1.f)
  222. target.draw(m_progressBar, states);
  223. target.draw(m_icon, states);
  224. target.draw(m_textTitle, states);
  225. target.draw(m_textProgress, states);
  226. if (!m_cancelFlag)
  227. {
  228. target.draw(m_textCancel, states);
  229. if (m_canSendTop && (m_status == Queued || m_status == Suspended || m_status == Downloading))
  230. target.draw(m_textSendTop, states);
  231. else if (m_status == Failed)
  232. target.draw(m_textRestart, states);
  233. }
  234. }
  235. const cpp3ds::Vector2f &Download::getSize() const
  236. {
  237. return m_size;
  238. }
  239. void Download::fillFromAppItem(std::shared_ptr<AppItem> app)
  240. {
  241. m_appItem = app;
  242. setProgressMessage(_("Queued"));
  243. if (Theme::isListItemBG9Themed)
  244. m_background.setTexture(&AssetManager<cpp3ds::Texture>::get(FREESHOP_DIR "/theme/images/listitembg.9.png"));
  245. else
  246. m_background.setTexture(&AssetManager<cpp3ds::Texture>::get("images/listitembg.9.png"));
  247. m_background.setContentSize(320.f + m_background.getPadding().width - m_background.getTexture()->getSize().x + 2.f, 24.f);
  248. m_size = m_background.getSize();
  249. float paddingRight = m_size.x - m_background.getContentSize().x - m_background.getPadding().left;
  250. float paddingBottom = m_size.y - m_background.getContentSize().y - m_background.getPadding().top;
  251. m_icon.setSize(cpp3ds::Vector2f(48.f, 48.f));
  252. cpp3ds::IntRect textureRect;
  253. m_icon.setTexture(app->getIcon(textureRect), true);
  254. m_icon.setTextureRect(textureRect);
  255. m_icon.setPosition(m_background.getPadding().left, m_background.getPadding().top);
  256. m_icon.setScale(0.5f, 0.5f);
  257. m_textCancel.setFont(AssetManager<cpp3ds::Font>::get("fonts/fontawesome.ttf"));
  258. m_textCancel.setString(L"\uf00d");
  259. m_textCancel.setCharacterSize(18);
  260. m_textCancel.setFillColor(cpp3ds::Color::White);
  261. m_textCancel.setOutlineColor(cpp3ds::Color(0, 0, 0, 200));
  262. m_textCancel.setOutlineThickness(1.f);
  263. m_textCancel.setOrigin(0, m_textCancel.getLocalBounds().top + m_textCancel.getLocalBounds().height / 2.f);
  264. m_textCancel.setPosition(m_size.x - paddingRight - m_textCancel.getLocalBounds().width,
  265. m_background.getPadding().top + m_background.getContentSize().y / 2.f);
  266. m_textSendTop = m_textCancel;
  267. m_textSendTop.setString(L"\uf077");
  268. m_textSendTop.move(-25.f, 0);
  269. m_textRestart = m_textCancel;
  270. m_textRestart.setString(L"\uf01e");
  271. m_textRestart.move(-25.f, 0);
  272. m_textTitle.setString(app->getTitle());
  273. m_textTitle.setCharacterSize(10);
  274. m_textTitle.setPosition(m_background.getPadding().left + 30.f, m_background.getPadding().top);
  275. if (Theme::isTextThemed)
  276. m_textTitle.setFillColor(Theme::primaryTextColor);
  277. else
  278. m_textTitle.setFillColor(cpp3ds::Color::Black);
  279. m_textTitle.useSystemFont();
  280. m_textProgress = m_textTitle;
  281. if (Theme::isTextThemed)
  282. m_textProgress.setFillColor(Theme::secondaryTextColor);
  283. else
  284. m_textProgress.setFillColor(cpp3ds::Color(130, 130, 130, 255));
  285. m_textProgress.move(0.f, 12.f);
  286. m_progressBar.setFillColor(cpp3ds::Color(97, 97, 97));
  287. m_progressBar.setOutlineColor(cpp3ds::Color(66, 66, 66));
  288. }
  289. void Download::processEvent(const cpp3ds::Event &event)
  290. {
  291. if (event.type == cpp3ds::Event::TouchBegan)
  292. {
  293. cpp3ds::FloatRect bounds = m_textCancel.getGlobalBounds();
  294. bounds.left += getPosition().x;
  295. bounds.top += getPosition().y + DownloadQueue::getInstance().getPosition().y;
  296. if (bounds.contains(event.touch.x, event.touch.y))
  297. {
  298. if (getStatus() != Downloading && getStatus() != Suspended)
  299. m_markedForDelete = true;
  300. else
  301. {
  302. g_browseState->requestStackPush(States::Dialog, false, [=](void *data) mutable {
  303. auto event = reinterpret_cast<DialogState::Event*>(data);
  304. if (event->type == DialogState::GetText)
  305. {
  306. auto str = reinterpret_cast<cpp3ds::String*>(event->data);
  307. *str = _("%s\n\nAre you sure you want to cancel this installation and lose all progress?", m_appItem->getTitle().toAnsiString().c_str());
  308. return true;
  309. }
  310. else if (event->type == DialogState::GetTitle) {
  311. auto str = reinterpret_cast<cpp3ds::String *>(event->data);
  312. *str = _("Cancel a download");
  313. return true;
  314. }
  315. else if (event->type == DialogState::Response)
  316. {
  317. bool *accepted = reinterpret_cast<bool*>(event->data);
  318. if (*accepted)
  319. {
  320. // Check status again in case it changed while dialog was open
  321. if (getStatus() == Downloading)
  322. {
  323. setProgressMessage(_("Canceling..."));
  324. cancel(false);
  325. }
  326. else if (getStatus() == Suspended)
  327. m_markedForDelete = true;
  328. }
  329. return true;
  330. }
  331. return false;
  332. });
  333. }
  334. }
  335. else if (m_canSendTop && m_onSendTop && (getStatus() == Queued || getStatus() == Suspended || getStatus() == Downloading))
  336. {
  337. bounds = m_textSendTop.getGlobalBounds();
  338. bounds.left += getPosition().x;
  339. bounds.top += getPosition().y + DownloadQueue::getInstance().getPosition().y;
  340. if (bounds.contains(event.touch.x, event.touch.y))
  341. {
  342. m_onSendTop(this);
  343. }
  344. }
  345. else if (getStatus() == Failed)
  346. {
  347. bounds = m_textRestart.getGlobalBounds();
  348. bounds.left += getPosition().x;
  349. bounds.top += getPosition().y + DownloadQueue::getInstance().getPosition().y;
  350. if (bounds.contains(event.touch.x, event.touch.y))
  351. {
  352. // Restart failed download
  353. DownloadQueue::getInstance().restartDownload(this);
  354. }
  355. }
  356. }
  357. }
  358. bool Download::markedForDelete()
  359. {
  360. return m_markedForDelete;
  361. }
  362. void Download::setDestination(const std::string &destination)
  363. {
  364. m_destination = destination;
  365. }
  366. void Download::setDataCallback(cpp3ds::Http::RequestCallback onData)
  367. {
  368. m_onData = onData;
  369. }
  370. void Download::setFinishCallback(DownloadFinishCallback onFinish)
  371. {
  372. m_onFinish = onFinish;
  373. }
  374. void Download::setSendTopCallback(SendTopCallback onSendTop)
  375. {
  376. m_onSendTop = onSendTop;
  377. }
  378. void Download::setField(const std::string &field, const std::string &value)
  379. {
  380. m_request.setField(field, value);
  381. }
  382. void Download::pushUrl(const std::string &url, cpp3ds::Uint64 position)
  383. {
  384. m_urlQueue.push(std::make_pair(url, position));
  385. }
  386. Download::Status Download::getStatus() const
  387. {
  388. return m_status;
  389. }
  390. void Download::suspend()
  391. {
  392. if (getStatus() != Downloading)
  393. return;
  394. cpp3ds::Lock lock(m_mutex);
  395. m_status = Suspended;
  396. }
  397. void Download::resume()
  398. {
  399. if (getStatus() == Suspended || getStatus() == Queued)
  400. {
  401. start();
  402. }
  403. }
  404. void Download::setRetryCount(int timesToRetry)
  405. {
  406. m_timesToRetry = timesToRetry;
  407. }
  408. const cpp3ds::Http::Response &Download::getLastResponse() const
  409. {
  410. return m_response;
  411. }
  412. void Download::setTimeout(cpp3ds::Time timeout)
  413. {
  414. m_timeout = timeout;
  415. }
  416. void Download::setBufferSize(size_t size)
  417. {
  418. if (size % 64 > 0)
  419. return;
  420. m_bufferSize = size;
  421. }
  422. void Download::update(float delta)
  423. {
  424. m_tweenManager.update(delta);
  425. }
  426. } // namespace FreeShop