DownloadQueue.cpp 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892
  1. #include <iostream>
  2. #include <string>
  3. #include <cpp3ds/System/I18n.hpp>
  4. #include <TweenEngine/Tween.h>
  5. #include <cpp3ds/System/Sleep.hpp>
  6. #include <cpp3ds/System/Err.hpp>
  7. #include <cpp3ds/System/FileSystem.hpp>
  8. #include <fstream>
  9. #include <rapidjson/ostreamwrapper.h>
  10. #include <rapidjson/writer.h>
  11. #include <cpp3ds/System/FileInputStream.hpp>
  12. #include <cpp3ds/System/Lock.hpp>
  13. #include <cmath>
  14. #include <ctime>
  15. #include "DownloadQueue.hpp"
  16. #include "Notification.hpp"
  17. #include "AppList.hpp"
  18. #include "TitleKeys.hpp"
  19. #include "InstalledList.hpp"
  20. #include "Config.hpp"
  21. #include "Util.hpp"
  22. #include "Theme.hpp"
  23. #include "States/SleepState.hpp"
  24. #include <inttypes.h>
  25. #ifndef EMULATION
  26. #include <3ds.h>
  27. #include "MCU/Mcu.hpp"
  28. #endif
  29. namespace FreeShop {
  30. DownloadItem::DownloadItem(std::shared_ptr<AppItem> appItem, Download *download, Installer *installer)
  31. : appItem(appItem)
  32. , download(download)
  33. , installer(installer)
  34. {
  35. }
  36. DownloadItem::~DownloadItem()
  37. {
  38. if (installer)
  39. delete installer;
  40. if (download)
  41. delete download;
  42. }
  43. DownloadQueue::DownloadQueue()
  44. : m_threadRefresh(&DownloadQueue::refresh, this)
  45. , m_refreshEnd(false)
  46. , m_isSleepInstalling(false)
  47. , m_size(320.f, 0.f)
  48. {
  49. if (Theme::isSoundChimeThemed)
  50. m_soundBufferFinish.loadFromFile(FREESHOP_DIR "/theme/sounds/chime.ogg");
  51. else
  52. m_soundBufferFinish.loadFromFile("sounds/chime.ogg");
  53. m_soundFinish.setBuffer(m_soundBufferFinish);
  54. load();
  55. m_threadRefresh.launch();
  56. }
  57. DownloadQueue::~DownloadQueue()
  58. {
  59. m_refreshEnd = true;
  60. cpp3ds::Lock lock(m_mutexRefresh);
  61. m_downloads.clear();
  62. }
  63. void DownloadQueue::addDownload(std::shared_ptr<AppItem> app, cpp3ds::Uint64 titleId, DownloadCompleteCallback callback, int contentIndex, float progress)
  64. {
  65. cpp3ds::Lock lock(m_mutexRefresh);
  66. cpp3ds::Uint32 type = titleId >> 32;
  67. if (cpp3ds::Keyboard::isKeyDown(cpp3ds::Keyboard::Select)) {
  68. app->queueForSleepInstall(false, type == TitleKeys::Demo);
  69. return;
  70. }
  71. if (titleId == 0)
  72. {
  73. if (app->isInstalled()) // Don't allow reinstalling without deleting
  74. return;
  75. titleId = app->getTitleId();
  76. }
  77. #ifndef EMULATION
  78. //Check if the title is not a system app (because it pass the verification for some reason)
  79. size_t titleType = app->getTitleId() >> 32;
  80. if (titleType != TitleKeys::SystemApplet && titleType != TitleKeys::SystemApplication) {
  81. bool isRegistered;
  82. NIMS_IsTaskRegistered(app->getTitleId(), &isRegistered);
  83. if (isRegistered || app->isSleepBusy()) {
  84. Notification::spawn(_("Registered for sleep download: \n%s", app->getTitle().toAnsiString().c_str()));
  85. return;
  86. }
  87. }
  88. #endif
  89. cpp3ds::String title = app->getTitle();
  90. if (type != TitleKeys::Game)
  91. {
  92. if (type == TitleKeys::Update)
  93. title = _("[Update] %s", title.toAnsiString().c_str());
  94. else if (type == TitleKeys::Demo)
  95. title = _("[Demo] %s", title.toAnsiString().c_str());
  96. else if (type == TitleKeys::DLC)
  97. title = _("[DLC] %s", title.toAnsiString().c_str());
  98. }
  99. std::string url = _("http://ccs.cdn.c.shop.nintendowifi.net/ccs/download/%016llX/tmd", titleId);
  100. Download* download = new Download(url);
  101. download->fillFromAppItem(app);
  102. download->m_textTitle.setString(title);
  103. download->setPosition(0.f, 240.f);
  104. download->setRetryCount(INT_MAX);
  105. download->setTimeout(cpp3ds::seconds(Config::get(Config::DownloadTimeout).GetFloat()));
  106. download->setBufferSize(1024 * Config::get(Config::DownloadBufferSize).GetUint());
  107. download->setSendTopCallback([this](Download *d){
  108. sendTop(d);
  109. });
  110. std::vector<char> buf; // Store fetched files (only used for TMD atm)
  111. cpp3ds::Clock clock;
  112. float count = 0;
  113. int fileIndex = 0; // Keep track of the multiple files, first is usually the TMD with rest being contents
  114. size_t fileSize = 0; // Size of each file (as obtain by http Content-Length)
  115. Installer *installer = new Installer(titleId, contentIndex);
  116. cpp3ds::Uint64 titleFileSize = 0;
  117. cpp3ds::Uint64 totalProcessed;
  118. // Is resuming from saved queue
  119. bool isResuming = contentIndex >= 0;
  120. // TMD values
  121. cpp3ds::Uint16 contentCount;
  122. cpp3ds::Uint16 titleVersion;
  123. std::vector<cpp3ds::Uint16> contentIndices;
  124. download->setDataCallback([=](const void* data, size_t len, size_t processed, const cpp3ds::Http::Response& response) mutable
  125. {
  126. // This condition should be true when download first starts.
  127. if (len == processed) {
  128. std::string length = response.getField("Content-Length");
  129. if (!length.empty())
  130. fileSize = strtoul(length.c_str(), 0, 10);
  131. }
  132. const char *bufdata = reinterpret_cast<const char*>(data);
  133. if (fileIndex == 0)
  134. {
  135. buf.insert(buf.end(), bufdata, bufdata + len);
  136. if (processed == fileSize && fileSize != 0)
  137. {
  138. static int dataOffsets[6] = {0x240, 0x140, 0x80, 0x240, 0x140, 0x80};
  139. char sigType = buf[0x3];
  140. titleVersion = *(cpp3ds::Uint16*)&buf[dataOffsets[sigType] + 0x9C];
  141. contentCount = __builtin_bswap16(*(cpp3ds::Uint16*)&buf[dataOffsets[sigType] + 0x9E]);
  142. if (isResuming)
  143. installer->resume();
  144. bool foundIndex = false; // For resuming via contentIndex arg
  145. for (int i = 0; i < contentCount; ++i)
  146. {
  147. char *contentChunk = &buf[dataOffsets[sigType] + 0x9C4 + (i * 0x30)];
  148. cpp3ds::Uint32 contentId = __builtin_bswap32(*(cpp3ds::Uint32*)&contentChunk[0]);
  149. cpp3ds::Uint16 contentIdx = __builtin_bswap16(*(cpp3ds::Uint16*)&contentChunk[4]);
  150. cpp3ds::Uint64 contentSize = __builtin_bswap64(*(cpp3ds::Uint64*)&contentChunk[8]);
  151. titleFileSize += contentSize;
  152. contentIndices.push_back(contentIdx);
  153. if (contentIdx == contentIndex)
  154. {
  155. foundIndex = true;
  156. fileIndex = i + 1;
  157. }
  158. if (!isResuming || foundIndex)
  159. download->pushUrl(_("http://ccs.cdn.c.shop.nintendowifi.net/ccs/download/%016llX/%08lX", titleId, contentId), (contentIdx == contentIndex) ? installer->getCurrentContentPosition() : 0);
  160. }
  161. totalProcessed = progress * titleFileSize;
  162. if (!isResuming)
  163. {
  164. // Check for cancel at each stage in case it changes
  165. if (!download->isCanceled())
  166. {
  167. download->setProgressMessage(_("Installing ticket..."));
  168. if (!installer->installTicket(titleVersion))
  169. return false;
  170. }
  171. cpp3ds::Uint32 type = titleId >> 32;
  172. if (!download->isCanceled() && type == TitleKeys::Game && !app->getSeed().empty())
  173. {
  174. download->setProgressMessage(_("Installing seed..."));
  175. if (!installer->installSeed(&app->getSeed()[0]))
  176. return false;
  177. }
  178. if (!download->isCanceled())
  179. {
  180. if (!installer->start(true))
  181. return false;
  182. download->setProgressMessage(_("Installing TMD..."));
  183. if (!installer->installTmd(&buf[0], dataOffsets[sigType] + 0x9C4 + (contentCount * 0x30)))
  184. return false;
  185. if (!installer->finalizeTmd())
  186. return false;
  187. }
  188. }
  189. buf.clear();
  190. if (!isResuming)
  191. fileIndex++;
  192. }
  193. }
  194. else // is a Content file
  195. {
  196. // Conditions indicate download issue (e.g. internet is down)
  197. // with either an empty length or one not 64-byte aligned
  198. #ifdef _3DS
  199. if (len == 0 || len % 64 > 0)
  200. {
  201. cpp3ds::Lock lock(m_mutexRefresh);
  202. download->suspend();
  203. installer->suspend();
  204. return true;
  205. }
  206. #endif
  207. int oldIndex = installer->getCurrentContentIndex();
  208. if (!installer->installContent(data, len, contentIndices[fileIndex-1]))
  209. {
  210. if (download->getStatus() == Download::Suspended)
  211. return true;
  212. return false;
  213. }
  214. // Save index change to help recover queue from crash
  215. if (oldIndex != installer->getCurrentContentIndex())
  216. save();
  217. totalProcessed += len;
  218. download->setProgress(static_cast<double>(totalProcessed) / titleFileSize);
  219. if (processed == fileSize && fileSize != 0)
  220. {
  221. if (!installer->finalizeContent())
  222. return false;
  223. if (fileIndex == 1 && type == TitleKeys::DLC)
  224. {
  225. download->setProgressMessage(_("Importing content..."));
  226. if (!installer->importContents(contentIndices.size() - 1, &contentIndices[1]))
  227. return false;
  228. }
  229. fileIndex++;
  230. }
  231. }
  232. // Handle status message and counters
  233. count += len;
  234. float speed = count / clock.getElapsedTime().asSeconds() / 1024.f;
  235. int secsRemaining = (titleFileSize - totalProcessed) / 1024 / speed;
  236. if (fileIndex <= contentCount)
  237. download->setProgressMessage(_("Installing %d/%d... %.1f%% (%.0f KB/s) %dm %02ds",
  238. fileIndex, contentCount,
  239. download->getProgress() * 100.f,
  240. speed, secsRemaining / 60, secsRemaining % 60));
  241. if (clock.getElapsedTime() > cpp3ds::seconds(5.f))
  242. {
  243. count = 0;
  244. clock.restart();
  245. }
  246. return true;
  247. });
  248. download->setFinishCallback([=](bool canceled, bool failed) mutable
  249. {
  250. bool succeeded = false;
  251. switch (download->getStatus())
  252. {
  253. case Download::Suspended:
  254. download->setProgressMessage(_("Suspended"));
  255. if (download->getLastResponse().getStatus() == cpp3ds::Http::Response::NotFound)
  256. download->m_status = Download::Failed;
  257. else
  258. return;
  259. case Download::Finished:
  260. if (installer->commit())
  261. {
  262. Notification::spawn(_("Completed: %s", app->getTitle().toAnsiString().c_str()));
  263. download->setProgressMessage(_("Installed"));
  264. succeeded = true;
  265. #ifndef EMULATION
  266. if (Config::get(Config::NEWSDownloadFinished).GetBool()) {
  267. //Get the time to print it on Notification
  268. time_t t = time(NULL);
  269. struct tm * timeinfo;
  270. timeinfo = localtime(&t);
  271. char timeTextFmt[20];
  272. strftime(timeTextFmt, 20, "%d/%m/%Y %H:%M:%S", timeinfo);
  273. //Get the formatted game size
  274. std::string gameSize;
  275. if (app->getFilesize() > 1024 * 1024 * 1024)
  276. gameSize = _("%.1f GB", static_cast<float>(app->getFilesize()) / 1024.f / 1024.f / 1024.f).toAnsiString();
  277. else if (app->getFilesize() > 1024 * 1024)
  278. gameSize = _("%.1f MB", static_cast<float>(app->getFilesize()) / 1024.f / 1024.f).toAnsiString();
  279. else
  280. gameSize = _("%.1f KB", static_cast<float>(app->getFilesize()) / 1024).toAnsiString();
  281. //No blink functionnality
  282. bool canBlink = true;
  283. if (Config::get(Config::NEWSNoLED).GetBool() || Config::get(Config::LEDDownloadFinished).GetBool())
  284. canBlink = false;
  285. Notification::sendNews(_("%s has been installed", app->getTitle().toAnsiString().c_str()), _("The game %s (%s) has been installed on \n%s", app->getTitle().toAnsiString().c_str(), gameSize, timeTextFmt));
  286. if (!canBlink)
  287. MCU::getInstance().ledReset();
  288. }
  289. if (Config::get(Config::LEDDownloadFinished).GetBool())
  290. MCU::getInstance().ledBlinkOnce(0xFFA419);
  291. #endif
  292. break;
  293. }
  294. download->m_status = Download::Failed;
  295. // Fall through
  296. case Download::Failed:
  297. Notification::spawn(_("Failed: %s", app->getTitle().toAnsiString().c_str()));
  298. installer->abort();
  299. switch (installer->getErrorCode()) {
  300. case 0: // Failed due to internet problem and not from Installer
  301. download->setProgressMessage(_("Failed: Internet problem"));
  302. break;
  303. case 0xC8A08035: download->setProgressMessage(_("Not enough space on NAND")); break;
  304. case 0xC86044D2: download->setProgressMessage(_("Not enough space on SD")); break;
  305. case 0xD8E0806A: download->setProgressMessage(_("Wrong title key")); break;
  306. case 0xD8A08004: download->setProgressMessage(_("Incomplete installation")); break;
  307. default:
  308. download->setProgressMessage(installer->getErrorString());
  309. }
  310. break;
  311. case Download::Canceled:
  312. break;
  313. }
  314. download->setProgress(1.f);
  315. // Reset CanSendTop state
  316. for (auto& download : m_downloads)
  317. download->download->m_canSendTop = true;
  318. // Refresh installed list to add recent install
  319. if (succeeded)
  320. InstalledList::getInstance().refresh();
  321. // Play sound if queue is finished
  322. if (getActiveCount() == 0 && !canceled)
  323. if ((Config::get(Config::PlaySoundAfterDownload).GetBool() && !SleepState::isSleeping) || (SleepState::isSleeping && Config::get(Config::PlaySoundAfterDownload).GetBool() && Config::get(Config::SoundOnInactivity).GetBool()))
  324. m_soundFinish.play();
  325. if (callback)
  326. callback(succeeded);
  327. });
  328. if (progress > 0.f)
  329. {
  330. download->setProgress(progress);
  331. download->setProgressMessage(_("Suspended"));
  332. download->m_status = Download::Suspended;
  333. }
  334. else
  335. download->setProgressMessage(_("Queued"));
  336. std::unique_ptr<DownloadItem> downloadItem(new DownloadItem(app, download, installer));
  337. m_downloads.emplace_back(std::move(downloadItem));
  338. realign();
  339. }
  340. void DownloadQueue::draw(cpp3ds::RenderTarget &target, cpp3ds::RenderStates states) const
  341. {
  342. states.transform *= getTransform();
  343. states.scissor = cpp3ds::UintRect(0, 30, 320, 210);
  344. for (auto& item : m_downloads)
  345. {
  346. static float top = 30.f - item->download->getSize().y;
  347. float posY = item->download->getPosition().y + m_scrollPos;
  348. if (posY > top && posY < 240.f)
  349. target.draw(*item->download, states);
  350. }
  351. }
  352. void DownloadQueue::restartDownload(Download *download)
  353. {
  354. for (auto& item : m_downloads)
  355. if (item->download == download && item->download->getStatus() == Download::Failed)
  356. {
  357. item->download->m_markedForDelete = true;
  358. addDownload(item->appItem, item->installer->getTitleId());
  359. break;
  360. }
  361. }
  362. bool DownloadQueue::isDownloading(std::shared_ptr<AppItem> app)
  363. {
  364. for (auto& item : m_downloads)
  365. if (item->appItem == app)
  366. {
  367. auto status = item->download->getStatus();
  368. if (status == Download::Queued || status == Download::Downloading || status == Download::Suspended)
  369. return true;
  370. }
  371. return false;
  372. }
  373. bool DownloadQueue::isDownloading(cpp3ds::Uint64 titleId)
  374. {
  375. for (auto& item : m_downloads)
  376. if (item->installer->getTitleId() == titleId)
  377. {
  378. auto status = item->download->getStatus();
  379. if (status == Download::Queued || status == Download::Downloading || status == Download::Suspended)
  380. return true;
  381. }
  382. return false;
  383. }
  384. bool DownloadQueue::processEvent(const cpp3ds::Event &event)
  385. {
  386. for (auto& item : m_downloads)
  387. {
  388. static float top = 30.f - item->download->getSize().y;
  389. float posY = item->download->getPosition().y + m_scrollPos;
  390. if (posY > top && posY < 240.f)
  391. item->download->processEvent(event);
  392. }
  393. return true;
  394. }
  395. void DownloadQueue::realign()
  396. {
  397. bool processedFirstItem = false;
  398. m_size.y = 0.f;
  399. for (int i = 0; i < m_downloads.size(); ++i)
  400. {
  401. Download *download = m_downloads[i]->download;
  402. Download::Status status = download->getStatus();
  403. if (download->markedForDelete())
  404. continue;
  405. if (processedFirstItem)
  406. {
  407. download->m_canSendTop = (status == Download::Queued || status == Download::Suspended || status == Download::Downloading);
  408. }
  409. else if (status == Download::Queued || status == Download::Suspended || status == Download::Downloading)
  410. {
  411. processedFirstItem = true;
  412. download->m_canSendTop = false;
  413. }
  414. TweenEngine::Tween::to(*download, util3ds::TweenSprite::POSITION_Y, 0.2f)
  415. .target(33.f + i * download->getSize().y)
  416. .start(m_tweenManager);
  417. m_size.y += download->getSize().y;
  418. }
  419. updateScrollSize();
  420. save();
  421. }
  422. void DownloadQueue::update(float delta)
  423. {
  424. // Remove downloads marked for delete
  425. {
  426. bool changed = false;
  427. cpp3ds::Lock lock(m_mutexRefresh);
  428. for (auto it = m_downloads.begin(); it != m_downloads.end();)
  429. {
  430. Download *download = it->get()->download;
  431. if (download->markedForDelete())
  432. {
  433. if (download->getStatus() == Download::Suspended)
  434. it->get()->installer->abort();
  435. m_downloads.erase(it);
  436. changed = true;
  437. }
  438. else
  439. ++it;
  440. if (!download->markedForDelete())
  441. download->update(delta);
  442. }
  443. if (changed)
  444. realign();
  445. }
  446. m_tweenManager.update(delta);
  447. }
  448. size_t DownloadQueue::getCount()
  449. {
  450. return m_downloads.size();
  451. }
  452. size_t DownloadQueue::getActiveCount()
  453. {
  454. size_t count = 0;
  455. for (auto& item : m_downloads)
  456. if (item->download->getProgress() < 1.f)
  457. ++count;
  458. return count;
  459. }
  460. DownloadQueue &DownloadQueue::getInstance()
  461. {
  462. static DownloadQueue downloadQueue;
  463. return downloadQueue;
  464. }
  465. void DownloadQueue::sendTop(Download *download)
  466. {
  467. cpp3ds::Lock lock(m_mutexRefresh);
  468. auto iterTopDownload = m_downloads.end();
  469. auto iterBottomDownload = m_downloads.end();
  470. for (auto it = m_downloads.begin(); it != m_downloads.end(); ++it)
  471. {
  472. if (iterTopDownload == m_downloads.end())
  473. {
  474. Download::Status status = it->get()->download->getStatus();
  475. if (status == Download::Downloading || status == Download::Queued || status == Download::Suspended)
  476. iterTopDownload = it;
  477. }
  478. else if (iterBottomDownload == m_downloads.end())
  479. {
  480. if (it->get()->download == download)
  481. iterBottomDownload = it;
  482. }
  483. }
  484. if (iterTopDownload != m_downloads.end() && iterBottomDownload != m_downloads.end())
  485. {
  486. std::rotate(iterTopDownload, iterBottomDownload, iterBottomDownload + 1);
  487. realign();
  488. m_clockRefresh.restart();
  489. }
  490. }
  491. void DownloadQueue::refresh()
  492. {
  493. while (!m_refreshEnd)
  494. {
  495. {
  496. cpp3ds::Lock lock(m_mutexRefresh);
  497. DownloadItem *activeDownload = nullptr;
  498. DownloadItem *firstQueued = nullptr;
  499. bool activeNeedsSuspension = false;
  500. for (auto& item : m_downloads)
  501. {
  502. Download::Status status = item->download->getStatus();
  503. if (!firstQueued && (status == Download::Queued || status == Download::Suspended))
  504. {
  505. firstQueued = item.get();
  506. }
  507. else if (!activeDownload && status == Download::Downloading)
  508. {
  509. activeDownload = item.get();
  510. if (firstQueued)
  511. activeNeedsSuspension = true;
  512. }
  513. }
  514. if ((!activeDownload && firstQueued) || activeNeedsSuspension)
  515. {
  516. if (activeNeedsSuspension)
  517. {
  518. activeDownload->download->suspend();
  519. activeDownload->installer->suspend();
  520. }
  521. firstQueued->installer->resume();
  522. firstQueued->download->resume();
  523. realign();
  524. }
  525. }
  526. m_clockRefresh.restart();
  527. while (m_clockRefresh.getElapsedTime() < cpp3ds::seconds(1.f))
  528. cpp3ds::sleep(cpp3ds::milliseconds(200));
  529. }
  530. }
  531. void DownloadQueue::suspend()
  532. {
  533. cpp3ds::Lock lock(m_mutexRefresh);
  534. m_refreshEnd = true;
  535. if (m_isSleepInstalling)
  536. m_sleepInstaller->suspend();
  537. for (auto& item : m_downloads)
  538. {
  539. item->download->suspend();
  540. item->installer->suspend();
  541. }
  542. }
  543. void DownloadQueue::resume()
  544. {
  545. m_refreshEnd = false;
  546. if (m_isSleepInstalling)
  547. m_sleepInstaller->resume();
  548. m_threadRefresh.launch();
  549. }
  550. void DownloadQueue::save()
  551. {
  552. rapidjson::Document json;
  553. std::string filepath = cpp3ds::FileSystem::getFilePath(FREESHOP_DIR "/queue.json");
  554. std::ofstream file(filepath);
  555. rapidjson::OStreamWrapper osw(file);
  556. rapidjson::Writer<rapidjson::OStreamWrapper> writer(osw);
  557. std::vector<std::string> subTitleIds; // Strings need to be in memory for json allocator write
  558. json.SetArray();
  559. for (auto& item : m_downloads)
  560. {
  561. Download::Status status = item->download->getStatus();
  562. if (status != Download::Downloading && status != Download::Queued && status != Download::Suspended)
  563. continue;
  564. rapidjson::Value obj(rapidjson::kObjectType);
  565. cpp3ds::Uint32 titleType = item->installer->getTitleId() >> 32;
  566. subTitleIds.push_back(_("%016llX", item->installer->getTitleId()).toAnsiString());
  567. obj.AddMember("title_id", rapidjson::StringRef(item->appItem->getTitleIdStr().c_str()), json.GetAllocator());
  568. obj.AddMember("subtitle_id", rapidjson::StringRef(subTitleIds.back().c_str()), json.GetAllocator());
  569. // TODO: Figure out how to properly resume DLC installation
  570. if (titleType == TitleKeys::DLC) {
  571. obj.AddMember("content_index", rapidjson::Value().SetInt(-1), json.GetAllocator());
  572. obj.AddMember("progress", rapidjson::Value().SetFloat(0.f), json.GetAllocator());
  573. } else {
  574. obj.AddMember("content_index", rapidjson::Value().SetInt(status == Download::Queued ? -1 : item->installer->getCurrentContentIndex()), json.GetAllocator());
  575. obj.AddMember("progress", rapidjson::Value().SetFloat(item->download->getProgress()), json.GetAllocator());
  576. }
  577. json.PushBack(obj, json.GetAllocator());
  578. }
  579. json.Accept(writer);
  580. }
  581. void DownloadQueue::load()
  582. {
  583. uint32_t pendingTitleCountSD = 0;
  584. uint32_t pendingTitleCountNAND = 0;
  585. std::vector<uint64_t> pendingTitleIds;
  586. #ifndef EMULATION
  587. Result res = 0;
  588. if (R_SUCCEEDED(res = AM_GetPendingTitleCount(&pendingTitleCountSD, MEDIATYPE_SD, AM_STATUS_MASK_INSTALLING)))
  589. if (R_SUCCEEDED(res = AM_GetPendingTitleCount(&pendingTitleCountNAND, MEDIATYPE_NAND, AM_STATUS_MASK_INSTALLING)))
  590. {
  591. pendingTitleIds.resize(pendingTitleCountSD + pendingTitleCountNAND);
  592. res = AM_GetPendingTitleList(nullptr, pendingTitleCountSD, MEDIATYPE_SD, AM_STATUS_MASK_INSTALLING, &pendingTitleIds[0]);
  593. res = AM_GetPendingTitleList(nullptr, pendingTitleCountNAND, MEDIATYPE_NAND, AM_STATUS_MASK_INSTALLING, &pendingTitleIds[pendingTitleCountSD]);
  594. }
  595. #endif
  596. cpp3ds::FileInputStream file;
  597. if (file.open(FREESHOP_DIR "/queue.json"))
  598. {
  599. rapidjson::Document json;
  600. std::string jsonStr;
  601. jsonStr.resize(file.getSize());
  602. file.read(&jsonStr[0], jsonStr.size());
  603. json.Parse(jsonStr.c_str());
  604. if (!json.HasParseError())
  605. {
  606. auto &list = AppList::getInstance().getList();
  607. for (auto it = json.Begin(); it != json.End(); ++it)
  608. {
  609. auto item = it->GetObject();
  610. std::string strTitleId = item["title_id"].GetString();
  611. std::string strSubTitleId = item["subtitle_id"].GetString();
  612. int contentIndex = item["content_index"].GetInt();
  613. float progress = item["progress"].GetFloat();
  614. uint64_t titleId = strtoull(strTitleId.c_str(), 0, 16);
  615. uint64_t subTitleId = strtoull(strSubTitleId.c_str(), 0, 16);
  616. size_t parentType = titleId >> 32;
  617. #ifdef _3DS
  618. for (auto pendingTitleId : pendingTitleIds)
  619. if (contentIndex == -1 || pendingTitleId == titleId || pendingTitleId == subTitleId)
  620. {
  621. #endif
  622. // For system titles, the relevant AppItem won't be in AppList
  623. if (parentType == TitleKeys::SystemApplet || parentType == TitleKeys::SystemApplication)
  624. for (auto &app : InstalledList::getInstance().getList())
  625. {
  626. if (app->getTitleId() == titleId)
  627. addDownload(app->getAppItem(), subTitleId, nullptr, contentIndex, progress);
  628. }
  629. else
  630. for (auto &app : list)
  631. if (app->getAppItem()->getTitleId() == titleId)
  632. addDownload(app->getAppItem(), subTitleId, nullptr, contentIndex, progress);
  633. #ifdef _3DS
  634. break;
  635. }
  636. #endif
  637. }
  638. }
  639. }
  640. }
  641. void DownloadQueue::addSleepDownload(std::shared_ptr<AppItem> app, cpp3ds::Uint64 titleId, cpp3ds::String title)
  642. {
  643. #ifndef EMULATION
  644. //Lock the thread so if user wants to install another app while an app is processed, this doesn't fu** up everything (and make ARM9 crashes (true story))
  645. cpp3ds::Lock lock(m_mutexSleepInstall);
  646. //A sleep installation is processed
  647. m_isSleepInstalling = true;
  648. //Get title type
  649. cpp3ds::Uint32 type = titleId >> 32;
  650. // Check if enough space to install the title
  651. FS_ArchiveResource resource = {0};
  652. Result ret;
  653. if (type == TitleKeys::DSiWare)
  654. ret = FSUSER_GetArchiveResource(&resource, SYSTEM_MEDIATYPE_TWL_NAND);
  655. else
  656. ret = FSUSER_GetArchiveResource(&resource, SYSTEM_MEDIATYPE_SD);
  657. if(R_SUCCEEDED(ret)) {
  658. u64 size = (u64) resource.freeClusters * (u64) resource.clusterSize;
  659. if (app->getFilesize() > size) {
  660. if (Config::get(Config::LEDDownloadError).GetBool())
  661. MCU::getInstance().ledBlinkThrice(0x1A25FF);
  662. if (type == TitleKeys::DSiWare)
  663. Notification::spawn(_("Not enough space on NAND to install: \n%s", app->getTitle().toAnsiString().c_str()));
  664. else
  665. Notification::spawn(_("Not enough space on SD card to install: \n%s", app->getTitle().toAnsiString().c_str()));
  666. return;
  667. }
  668. }
  669. if (titleId == 0)
  670. {
  671. if (app->isInstalled()) { // Don't allow reinstalling without deleting
  672. m_isSleepInstalling = false;
  673. return;
  674. }
  675. titleId = app->getTitleId();
  676. }
  677. bool isRegistered;
  678. NIMS_IsTaskRegistered(titleId, &isRegistered);
  679. //Get title name
  680. cpp3ds::String appTitle;
  681. if (title == "")
  682. appTitle = app->getTitle();
  683. else
  684. appTitle = title;
  685. //Add prefix to the game
  686. if (type == TitleKeys::Update)
  687. appTitle = _("[Update] %s", appTitle.toAnsiString().c_str());
  688. else if (type == TitleKeys::Demo)
  689. appTitle = _("[Demo] %s", appTitle.toAnsiString().c_str());
  690. else if (type == TitleKeys::DLC)
  691. appTitle = _("[DLC] %s", appTitle.toAnsiString().c_str());
  692. std::string stdAppTitle = appTitle.toAnsiString();
  693. if (isRegistered) {
  694. Notification::spawn(_("Ready for sleep installation: \n%s", app->getTitle().toAnsiString().c_str()));
  695. m_isSleepInstalling = false;
  696. return;
  697. }
  698. Notification::spawn(_("Processing sleep installation: \n%s", stdAppTitle.c_str()));
  699. //Some vars initialisation
  700. uint32_t res;
  701. NIM_TitleConfig tc;
  702. m_sleepInstaller = new Installer(titleId, 0);
  703. FS_MediaType mediaType = ((titleId >> 32) == TitleKeys::DSiWare) ? MEDIATYPE_NAND : MEDIATYPE_SD;
  704. cpp3ds::Uint32 titleType = titleId >> 32;
  705. //Get the title version (and not ticket version) via the title ID
  706. res = getTicketVersion(titleId);
  707. printf("%" PRIu32 "\n", res);
  708. //Check at each step that the user didn't went to HOME Menu
  709. if (m_refreshEnd) {
  710. while (m_refreshEnd)
  711. cpp3ds::sleep(cpp3ds::milliseconds(100));
  712. }
  713. //Install app ticket
  714. if (!m_sleepInstaller->installTicket(res)) {
  715. Notification::spawn(_("Can't install ticket: \n%s", stdAppTitle.c_str()));
  716. m_isSleepInstalling = false;
  717. return;
  718. }
  719. //Check at each step that the user didn't went to HOME Menu
  720. if (m_refreshEnd) {
  721. while (m_refreshEnd)
  722. cpp3ds::sleep(cpp3ds::milliseconds(100));
  723. }
  724. //Install app seed
  725. if (titleType == TitleKeys::Game && !app->getSeed().empty()) {
  726. if (!m_sleepInstaller->installSeed(&app->getSeed()[0])) {
  727. Notification::spawn(_("Can't install seed: \n%s", stdAppTitle.c_str()));
  728. m_isSleepInstalling = false;
  729. return;
  730. }
  731. }
  732. //Check at each step that the user didn't went to HOME Menu
  733. if (m_refreshEnd) {
  734. while (m_refreshEnd)
  735. cpp3ds::sleep(cpp3ds::milliseconds(100));
  736. }
  737. //The code that register the sleep download
  738. NIMS_MakeTitleConfig(&tc, titleId, res, 0, mediaType);
  739. if (R_SUCCEEDED(res = NIMS_RegisterTask(&tc, stdAppTitle.c_str(), "freeShop download"))) {
  740. Notification::spawn(_("Ready for sleep installation: \n%s", stdAppTitle.c_str()));
  741. if (Config::get(Config::LEDDownloadFinished).GetBool())
  742. MCU::getInstance().ledBlinkOnce(0x8EEBA0);
  743. } else {
  744. Notification::spawn(_("Sleep installation failed: \n%s", stdAppTitle.c_str()));
  745. if (Config::get(Config::LEDDownloadError).GetBool())
  746. MCU::getInstance().ledBlinkThrice(0x1A25FF);
  747. }
  748. /*The clear and simple explanation of this (LOOOONNNGGGGGGG) condition:
  749. If the "Play sound after downoad queue finishes" option IS enabled AND the freeShop is NOT on it's sleeping state, the sound can be played
  750. If the "Play sound after downoad queue finishes" option IS enabled AND the freeShop IS on it's sleeping state AND the "Allow sounds on inactivity mode" option IS enabled, the sound can be played*/
  751. if ((Config::get(Config::PlaySoundAfterDownload).GetBool() && !SleepState::isSleeping) || (SleepState::isSleeping && Config::get(Config::PlaySoundAfterDownload).GetBool() && Config::get(Config::SoundOnInactivity).GetBool()))
  752. m_soundFinish.play();
  753. m_isSleepInstalling = false;
  754. #endif
  755. }
  756. void DownloadQueue::setScroll(float position)
  757. {
  758. m_scrollPos = position;
  759. setPosition(0.f, std::round(position));
  760. }
  761. float DownloadQueue::getScroll()
  762. {
  763. return m_scrollPos;
  764. }
  765. const cpp3ds::Vector2f &DownloadQueue::getScrollSize()
  766. {
  767. return m_size;
  768. }
  769. } // namespace FreeShop