BioTorrents.de’s version of Gazelle
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

torrentsdl.class.php 9.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  1. <?php
  2. #declare(strict_types=1);
  3. /**
  4. * Class for functions related to the features involving torrent downloads
  5. */
  6. class TorrentsDL
  7. {
  8. const ChunkSize = 100;
  9. const MaxPathLength = 200;
  10. private $QueryResult;
  11. private $QueryRowNum = 0;
  12. private $Zip;
  13. private $IDBoundaries;
  14. private $FailedFiles = [];
  15. private $NumAdded = 0;
  16. private $NumFound = 0;
  17. private $Size = 0;
  18. private $Title;
  19. private $User;
  20. private $AnnounceURL;
  21. private $AnnounceList;
  22. /**
  23. * Create a Zip object and store the query results
  24. *
  25. * @param mysqli_result $QueryResult results from a query on the collector pages
  26. * @param string $Title name of the collection that will be created
  27. * @param string $AnnounceURL URL to add to the created torrents
  28. */
  29. public function __construct(&$QueryResult, $Title)
  30. {
  31. G::$Cache->InternalCache = false; // The internal cache is almost completely useless for this
  32. Zip::unlimit(); // Need more memory and longer timeout
  33. $this->QueryResult = $QueryResult;
  34. $this->Title = $Title;
  35. $this->User = G::$LoggedUser;
  36. $this->AnnounceURL = ANNOUNCE_URLS[0][0]."/".G::$LoggedUser['torrent_pass']."/announce";
  37. function add_passkey($Ann)
  38. {
  39. return (is_array($Ann)) ? array_map('add_passkey', $Ann) : $Ann."/".G::$LoggedUser['torrent_pass']."/announce";
  40. }
  41. # todo: Probably not working, but no need yet
  42. $this->AnnounceList = (sizeof(ANNOUNCE_URLS[0]) === 1 && sizeof(ANNOUNCE_URLS[0][0]) === 1) ? [] : array_map('add_passkey', ANNOUNCE_URLS[0]);
  43. # Tracker tiers (pending)
  44. #$this->AnnounceList = (sizeof(ANNOUNCE_URLS) === 1 && sizeof(ANNOUNCE_URLS[0]) === 1) ? [] : array(array_map('add_passkey', ANNOUNCE_URLS[0]), ANNOUNCE_URLS[1]);
  45. # Original Oppaitime
  46. #$this->AnnounceList = (sizeof(ANNOUNCE_URLS) == 1 && sizeof(ANNOUNCE_URLS[0]) == 1) ? [] : array_map('add_passkey', ANNOUNCE_URLS);
  47. $this->Zip = new Zip(Misc::file_string($Title));
  48. }
  49. /**
  50. * Store the results from a DB query in smaller chunks to save memory
  51. *
  52. * @param string $Key the key to use in the result hash map
  53. * @return array with results and torrent group IDs or false if there are no results left
  54. */
  55. public function get_downloads($Key)
  56. {
  57. $GroupIDs = $Downloads = [];
  58. $OldQuery = G::$DB->get_query_id();
  59. G::$DB->set_query_id($this->QueryResult);
  60. if (!isset($this->IDBoundaries)) {
  61. if ($Key === 'TorrentID') {
  62. $this->IDBoundaries = false;
  63. } else {
  64. $this->IDBoundaries = G::$DB->to_pair($Key, 'TorrentID', false);
  65. }
  66. }
  67. $Found = 0;
  68. while ($Download = G::$DB->next_record(MYSQLI_ASSOC, false)) {
  69. if (!$this->IDBoundaries || $Download['TorrentID'] === $this->IDBoundaries[$Download[$Key]]) {
  70. $Found++;
  71. $Downloads[$Download[$Key]] = $Download;
  72. $GroupIDs[$Download['TorrentID']] = $Download['GroupID'];
  73. if ($Found >= self::ChunkSize) {
  74. break;
  75. }
  76. }
  77. }
  78. $this->NumFound += $Found;
  79. G::$DB->set_query_id($OldQuery);
  80. if (empty($Downloads)) {
  81. return false;
  82. }
  83. return array($Downloads, $GroupIDs);
  84. }
  85. /**
  86. * Add a file to the zip archive
  87. *
  88. * @param string $TorrentData bencoded torrent without announce url (new format) or TORRENT object (old format)
  89. * @param array $Info file info stored as an array with at least the keys
  90. * Artist, Name, Year, Media, Format, Encoding and TorrentID
  91. * @param string $FolderName folder name
  92. */
  93. public function add_file(&$TorrentData, $Info, $FolderName = '')
  94. {
  95. $FolderName = Misc::file_string($FolderName);
  96. $MaxPathLength = $FolderName ? (self::MaxPathLength - strlen($FolderName) - 1) : self::MaxPathLength;
  97. $FileName = self::construct_file_name($Info['TorrentID'], $MaxPathLength);
  98. $this->Size += $Info['Size'];
  99. $this->NumAdded++;
  100. $this->Zip->add_file(self::get_file($TorrentData, $this->AnnounceURL, $this->AnnounceList), ($FolderName ? "$FolderName/" : "") . $FileName);
  101. usleep(25000); // We don't want to send much faster than the client can receive
  102. return;
  103. }
  104. /**
  105. * Add a file to the list of files that could not be downloaded
  106. *
  107. * @param array $Info file info stored as an array with at least the keys Artist, Name and Year
  108. */
  109. public function fail_file($Info)
  110. {
  111. $this->FailedFiles[] = $Info['Artist'] . ' - ' . $Info['Name'] . ' - ' . $Info['Year'];
  112. }
  113. /**
  114. * Add a file to the list of files that did not match the user's format or quality requirements
  115. *
  116. * @param array $Info file info stored as an array with at least the keys Artist, Name and Year
  117. */
  118. public function skip_file($Info)
  119. {
  120. $this->SkippedFiles[] = $Info['Artist'] . ' - ' . $Info['Name'] . ' - ' . $Info['Year'];
  121. }
  122. /**
  123. * Add a summary to the archive and include a list of files that could not be added. Close the zip archive
  124. *
  125. * @param bool $FilterStats whether to include filter stats in the report
  126. */
  127. public function finalize($FilterStats = true)
  128. {
  129. $this->Zip->add_file($this->summary($FilterStats), "Summary.txt");
  130. if (!empty($this->FailedFiles)) {
  131. $this->Zip->add_file($this->errors(), "Errors.txt");
  132. }
  133. $this->Zip->close_stream();
  134. }
  135. /**
  136. * Produce a summary text over the collector results
  137. *
  138. * @param bool $FilterStats whether to include filter stats in the report
  139. * @return summary text
  140. */
  141. public function summary($FilterStats)
  142. {
  143. $ENV = ENV::go;
  144. global $ScriptStartTime;
  145. $Time = number_format(1000 * (microtime(true) - $ScriptStartTime), 2)." ms";
  146. $Used = Format::get_size(memory_get_usage(true));
  147. $Date = date("M d Y, H:i");
  148. $NumSkipped = count($this->SkippedFiles);
  149. # wtf
  150. return "Collector Download Summary for $this->Title - $ENV->SITE_NAME\r\n"
  151. . "\r\n"
  152. . "User: {$this->User[Username]}\r\n"
  153. . "Passkey: {$this->User[torrent_pass]}\r\n"
  154. . "\r\n"
  155. . "Time: $Time\r\n"
  156. . "Used: $Used\r\n"
  157. . "Date: $Date\r\n"
  158. . "\r\n"
  159. . ($FilterStats !== false
  160. ? "Torrent groups analyzed: $this->NumFound\r\n"
  161. . "Torrent groups filtered: $NumSkipped\r\n"
  162. : "")
  163. . "Torrents downloaded: $this->NumAdded\r\n"
  164. . "\r\n"
  165. . "Total size of torrents (ratio hit): ".Format::get_size($this->Size)."\r\n"
  166. . ($NumSkipped
  167. ? "\r\n"
  168. . "Albums unavailable within your criteria (consider making a request for your desired format):\r\n"
  169. . implode("\r\n", $this->SkippedFiles) . "\r\n"
  170. : "");
  171. }
  172. /**
  173. * Compile a list of files that could not be added to the archive
  174. *
  175. * @return list of files
  176. */
  177. public function errors()
  178. {
  179. return "A server error occurred. Please try again at a later time.\r\n"
  180. . "\r\n"
  181. . "The following torrents could not be downloaded:\r\n"
  182. . implode("\r\n", $this->FailedFiles) . "\r\n";
  183. }
  184. /**
  185. * Combine a bunch of torrent info into a standardized file name
  186. *
  187. * @params most input variables are self-explanatory
  188. * @param int $TorrentID if given, append "-TorrentID" to torrent name
  189. * @param int $MaxLength maximum file name length
  190. * @return file name with at most $MaxLength characters
  191. */
  192. public static function construct_file_name($TorrentID = false, $MaxLength = self::MaxPathLength)
  193. {
  194. $MaxLength -= 8; // ".torrent"
  195. if ($TorrentID !== false) {
  196. $MaxLength -= (strlen($TorrentID) + 1);
  197. }
  198. return "$TorrentID.torrent";
  199. /*
  200. $TorrentArtist = Misc::file_string($Artist);
  201. $TorrentName = Misc::file_string($Album);
  202. */
  203. }
  204. /**
  205. * Convert a stored torrent into a binary file that can be loaded in a torrent client
  206. *
  207. * @param mixed $TorrentData bencoded torrent without announce URL (new format) or TORRENT object (old format)
  208. * @return bencoded string
  209. */
  210. public static function get_file(&$TorrentData, $AnnounceURL, $AnnounceList = [])
  211. {
  212. if (Misc::is_new_torrent($TorrentData)) {
  213. $Bencode = BencodeTorrent::add_announce_url($TorrentData, $AnnounceURL);
  214. if (!empty($AnnounceList)) {
  215. $Bencode = BencodeTorrent::add_announce_list($Bencode, $AnnounceList);
  216. }
  217. return $Bencode;
  218. }
  219. $Tor = new TORRENT(unserialize(base64_decode($TorrentData)), true);
  220. $Tor->set_announce_url($AnnounceURL);
  221. unset($Tor->Val['announce-list']);
  222. if (!empty($AnnounceList)) {
  223. $Tor->set_announce_list($AnnounceList);
  224. }
  225. unset($Tor->Val['url-list']);
  226. unset($Tor->Val['libtorrent_resume']);
  227. return $Tor->enc();
  228. }
  229. }