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.

torrents.class.php 49KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290
  1. <?php
  2. class Torrents
  3. {
  4. const FILELIST_DELIM = 0xF7; // Hex for &divide; Must be the same as phrase_boundary in sphinx.conf!
  5. const SNATCHED_UPDATE_INTERVAL = 3600; // How often we want to update users' snatch lists
  6. const SNATCHED_UPDATE_AFTERDL = 300; // How long after a torrent download we want to update a user's snatch lists
  7. /**
  8. * Function to get data and torrents for an array of GroupIDs. Order of keys doesn't matter
  9. *
  10. * @param array $GroupIDs
  11. * @param boolean $Return if false, nothing is returned. For priming cache.
  12. * @param boolean $GetArtists if true, each group will contain the result of
  13. * Artists::get_artists($GroupID), in result[$GroupID]['ExtendedArtists']
  14. * @param boolean $Torrents if true, each group contains a list of torrents, in result[$GroupID]['Torrents']
  15. *
  16. * @return array each row of the following format:
  17. * GroupID => (
  18. * ID
  19. * Name
  20. * Year
  21. * RecordLabel
  22. * CatalogueNumber
  23. * TagList
  24. * ReleaseType
  25. * VanityHouse
  26. * WikiImage
  27. * CategoryID
  28. * Torrents => {
  29. * ID => {
  30. * GroupID, Media, Format, Encoding, RemasterYear, Remastered,
  31. * RemasterTitle, RemasterRecordLabel, RemasterCatalogueNumber, Scene,
  32. * HasLog, HasCue, LogScore, FileCount, FreeTorrent, Size, Leechers,
  33. * Seeders, Snatched, Time, HasFile, PersonalFL, IsSnatched
  34. * }
  35. * }
  36. * Artists => {
  37. * {
  38. * id, name, aliasid // Only main artists
  39. * }
  40. * }
  41. * ExtendedArtists => {
  42. * [1-6] => { // See documentation on Artists::get_artists
  43. * id, name, aliasid
  44. * }
  45. * }
  46. * Flags => {
  47. * IsSnatched
  48. * }
  49. */
  50. public static function get_groups($GroupIDs, $Return = true, $GetArtists = true, $Torrents = true)
  51. {
  52. $Found = $NotFound = array_fill_keys($GroupIDs, false);
  53. $Key = $Torrents ? 'torrent_group_' : 'torrent_group_light_';
  54. foreach ($GroupIDs as $i => $GroupID) {
  55. if (!is_number($GroupID)) {
  56. unset($GroupIDs[$i], $Found[$GroupID], $NotFound[$GroupID]);
  57. continue;
  58. }
  59. $Data = G::$Cache->get_value($Key . $GroupID, true);
  60. if (!empty($Data) && is_array($Data) && $Data['ver'] === Cache::GROUP_VERSION) {
  61. unset($NotFound[$GroupID]);
  62. $Found[$GroupID] = $Data['d'];
  63. }
  64. }
  65. // Make sure there's something in $GroupIDs, otherwise the SQL will break
  66. if (count($GroupIDs) === 0) {
  67. return [];
  68. }
  69. /*
  70. Changing any of these attributes returned will cause very large, very dramatic site-wide chaos.
  71. Do not change what is returned or the order thereof without updating:
  72. torrents, artists, collages, bookmarks, better, the front page,
  73. and anywhere else the get_groups function is used.
  74. Update self::array_group(), too
  75. */
  76. if (count($NotFound) > 0) {
  77. $IDs = implode(',', array_keys($NotFound));
  78. $NotFound = [];
  79. $QueryID = G::$DB->get_query_id();
  80. G::$DB->query("
  81. SELECT
  82. ID, Name, NameRJ, NameJP, Year, CatalogueNumber, Pages, Studio, Series, DLSiteID, TagList, WikiImage, CategoryID
  83. FROM torrents_group
  84. WHERE ID IN ($IDs)");
  85. while ($Group = G::$DB->next_record(MYSQLI_ASSOC, true)) {
  86. $NotFound[$Group['ID']] = $Group;
  87. $NotFound[$Group['ID']]['Torrents'] = [];
  88. $NotFound[$Group['ID']]['Artists'] = [];
  89. }
  90. G::$DB->set_query_id($QueryID);
  91. /*
  92. $QueryID = G::$DB->get_query_id();
  93. G::$DB->query("
  94. SELECT
  95. ID, GroupID, UserID, Time, Image
  96. FROM torrents_screenshots
  97. WHERE GroupID IN ($IDs)");
  98. while ($Screenshot = G::$DB->next_record(MYSQLI_ASSOC, true)) {
  99. if (!isset($NotFound[$Screenshot['GroupID']]['Screenshots']))
  100. $NotFound[$Screenshot['GroupID']]['Screenshots'] = [];
  101. $NotFound[$Screenshot['GroupID']]['Screenshots'][] = $Screenshot;
  102. }
  103. G::$DB->set_query_id($QueryID);
  104. */
  105. if ($Torrents) {
  106. $QueryID = G::$DB->get_query_id();
  107. G::$DB->query("
  108. SELECT
  109. ID, GroupID, Media, Container, Codec, Resolution, AudioFormat,
  110. Language, Subbing, Subber, Censored, Archive, FileCount, FreeTorrent,
  111. Size, Leechers, Seeders, Snatched, Time, f.ExpiryTime, ID AS HasFile,
  112. FreeLeechType, hex(info_hash) as info_hash
  113. FROM torrents
  114. LEFT JOIN shop_freeleeches AS f ON f.TorrentID=ID
  115. WHERE GroupID IN ($IDs)
  116. ORDER BY GroupID, Media, Container, Codec, ID");
  117. while ($Torrent = G::$DB->next_record(MYSQLI_ASSOC, true)) {
  118. $NotFound[$Torrent['GroupID']]['Torrents'][$Torrent['ID']] = $Torrent;
  119. }
  120. G::$DB->set_query_id($QueryID);
  121. }
  122. foreach ($NotFound as $GroupID => $GroupInfo) {
  123. G::$Cache->cache_value($Key . $GroupID, array('ver' => Cache::GROUP_VERSION, 'd' => $GroupInfo), 0);
  124. }
  125. $Found = $NotFound + $Found;
  126. }
  127. // Filter out orphans (elements that are === false)
  128. $Found = array_filter($Found);
  129. if ($GetArtists) {
  130. $Artists = Artists::get_artists($GroupIDs);
  131. } else {
  132. $Artists = [];
  133. }
  134. if ($Return) { // If we're interested in the data, and not just caching it
  135. foreach ($Artists as $GroupID => $Data) {
  136. if (!isset($Found[$GroupID])) {
  137. continue;
  138. }
  139. $Found[$GroupID]['Artists'] = $Data;
  140. }
  141. // Fetch all user specific torrent properties
  142. if ($Torrents) {
  143. foreach ($Found as &$Group) {
  144. $Group['Flags'] = array('IsSnatched' => false, 'IsSeeding' => false, 'IsLeeching' => false);
  145. if (!empty($Group['Torrents'])) {
  146. foreach ($Group['Torrents'] as &$Torrent) {
  147. self::torrent_properties($Torrent, $Group['Flags']);
  148. }
  149. }
  150. }
  151. }
  152. return $Found;
  153. }
  154. }
  155. /**
  156. * Returns a reconfigured array from a Torrent Group
  157. *
  158. * Use this with extract() instead of the volatile list($GroupID, ...)
  159. * Then use the variables $GroupID, $GroupName, etc
  160. *
  161. * @example extract(Torrents::array_group($SomeGroup));
  162. * @param array $Group torrent group
  163. * @return array Re-key'd array
  164. */
  165. public static function array_group(array &$Group)
  166. {
  167. return array(
  168. 'GroupID' => $Group['ID'],
  169. 'GroupName' => $Group['Name'],
  170. 'GroupNameRJ' => $Group['NameRJ'],
  171. 'GroupNameJP' => $Group['NameJP'],
  172. 'GroupYear' => $Group['Year'],
  173. 'GroupCategoryID' => $Group['CategoryID'],
  174. 'GroupCatalogueNumber' => $Group['CatalogueNumber'],
  175. 'GroupPages' => $Group['Pages'],
  176. 'GroupDLSiteID' => ($Group['DLSiteID'] ?? ''),
  177. 'GroupStudio' => $Group['Studio'],
  178. 'GroupSeries' => $Group['Series'],
  179. 'GroupFlags' => ($Group['Flags'] ?? ''),
  180. 'TagList' => $Group['TagList'],
  181. 'WikiImage' => $Group['WikiImage'],
  182. 'Torrents' => $Group['Torrents'],
  183. 'Artists' => $Group['Artists']
  184. );
  185. }
  186. /**
  187. * Supplements a torrent array with information that only concerns certain users and therefore cannot be cached
  188. *
  189. * @param array $Torrent torrent array preferably in the form used by Torrents::get_groups() or get_group_info()
  190. * @param int $TorrentID
  191. */
  192. public static function torrent_properties(&$Torrent, &$Flags)
  193. {
  194. # FL Token
  195. $Torrent['PersonalFL'] = empty($Torrent['FreeTorrent']) && self::has_token($Torrent['ID']);
  196. # Snatched
  197. if ($Torrent['IsSnatched'] = self::has_snatched($Torrent['ID'])) {
  198. $Flags['IsSnatched'] = true;
  199. } else {
  200. $Flags['IsSnatched'] = false;
  201. }
  202. # Seeding
  203. if ($Torrent['IsSeeding'] = self::is_seeding($Torrent['ID'])) {
  204. $Flags['IsSeeding'] = true;
  205. } else {
  206. $Flags['IsSeeding'] = false;
  207. }
  208. # Leeching
  209. if ($Torrent['IsLeeching'] = self::is_leeching($Torrent['ID'])) {
  210. $Flags['IsLeeching'] = true;
  211. } else {
  212. $Flags['IsLeeching'] = false;
  213. }
  214. }
  215. /*
  216. * Write to the group log.
  217. *
  218. * @param int $GroupID
  219. * @param int $TorrentID
  220. * @param int $UserID
  221. * @param string $Message
  222. * @param boolean $Hidden Currently does fuck all.
  223. *
  224. * todo: Fix that
  225. */
  226. public static function write_group_log($GroupID, $TorrentID, $UserID, $Message, $Hidden)
  227. {
  228. global $Time;
  229. $QueryID = G::$DB->get_query_id();
  230. G::$DB->query(
  231. "
  232. INSERT INTO group_log
  233. (GroupID, TorrentID, UserID, Info, Time, Hidden)
  234. VALUES
  235. (?, ?, ?, ?, NOW(), ?)",
  236. $GroupID,
  237. $TorrentID,
  238. $UserID,
  239. $Message,
  240. $Hidden
  241. );
  242. G::$DB->set_query_id($QueryID);
  243. }
  244. /**
  245. * Delete a torrent.
  246. *
  247. * @param int $ID The ID of the torrent to delete.
  248. * @param int $GroupID Set it if you have it handy, to save a query. Otherwise, it will be found.
  249. * @param string $OcelotReason The deletion reason for ocelot to report to users.
  250. */
  251. public static function delete_torrent($ID, $GroupID = 0, $OcelotReason = -1)
  252. {
  253. $QueryID = G::$DB->get_query_id();
  254. if (!$GroupID) {
  255. G::$DB->query("
  256. SELECT GroupID, UserID
  257. FROM torrents
  258. WHERE ID = '$ID'");
  259. list($GroupID, $UploaderID) = G::$DB->next_record();
  260. }
  261. if (empty($UserID)) {
  262. G::$DB->query("
  263. SELECT UserID
  264. FROM torrents
  265. WHERE ID = '$ID'");
  266. list($UserID) = G::$DB->next_record();
  267. }
  268. $RecentUploads = G::$Cache->get_value("recent_uploads_$UserID");
  269. if (is_array($RecentUploads)) {
  270. foreach ($RecentUploads as $Key => $Recent) {
  271. if ($Recent['ID'] == $GroupID) {
  272. G::$Cache->delete_value("recent_uploads_$UserID");
  273. }
  274. }
  275. }
  276. G::$DB->query("
  277. SELECT info_hash
  278. FROM torrents
  279. WHERE ID = $ID");
  280. list($InfoHash) = G::$DB->next_record(MYSQLI_BOTH, false);
  281. G::$DB->query("
  282. DELETE FROM torrents
  283. WHERE ID = $ID");
  284. Tracker::update_tracker('delete_torrent', array('info_hash' => rawurlencode($InfoHash), 'id' => $ID, 'reason' => $OcelotReason));
  285. G::$Cache->decrement('stats_torrent_count');
  286. G::$DB->query("
  287. SELECT COUNT(ID)
  288. FROM torrents
  289. WHERE GroupID = '$GroupID'");
  290. list($Count) = G::$DB->next_record();
  291. if ($Count == 0) {
  292. Torrents::delete_group($GroupID);
  293. } else {
  294. Torrents::update_hash($GroupID);
  295. }
  296. // Torrent notifications
  297. G::$DB->query("
  298. SELECT UserID
  299. FROM users_notify_torrents
  300. WHERE TorrentID = '$ID'");
  301. while (list($UserID) = G::$DB->next_record()) {
  302. G::$Cache->delete_value("notifications_new_$UserID");
  303. }
  304. G::$DB->query("
  305. DELETE FROM users_notify_torrents
  306. WHERE TorrentID = '$ID'");
  307. G::$DB->query("
  308. UPDATE reportsv2
  309. SET
  310. Status = 'Resolved',
  311. LastChangeTime = NOW(),
  312. ModComment = 'Report already dealt with (torrent deleted)'
  313. WHERE TorrentID = ?
  314. AND Status != 'Resolved'", $ID);
  315. $Reports = G::$DB->affected_rows();
  316. if ($Reports) {
  317. G::$Cache->decrement('num_torrent_reportsv2', $Reports);
  318. }
  319. unlink(TORRENT_STORE.$ID.'.torrent');
  320. G::$DB->query("
  321. DELETE FROM torrents_bad_tags
  322. WHERE TorrentID = ?", $ID);
  323. G::$DB->query("
  324. DELETE FROM torrents_bad_folders
  325. WHERE TorrentID = ?", $ID);
  326. G::$DB->query("
  327. DELETE FROM torrents_bad_files
  328. WHERE TorrentID = ?", $ID);
  329. G::$DB->query("
  330. DELETE FROM shop_freeleeches
  331. WHERE TorrentID = ?", $ID);
  332. $FLs = G::$DB->affected_rows();
  333. if ($FLs) {
  334. G::$Cache->delete_value('shop_freeleech_list');
  335. }
  336. // Tells Sphinx that the group is removed
  337. G::$DB->query("
  338. REPLACE INTO sphinx_delta (ID, Time)
  339. VALUES (?, UNIX_TIMESTAMP())", $ID);
  340. G::$Cache->delete_value("torrent_download_$ID");
  341. G::$Cache->delete_value("torrent_group_$GroupID");
  342. G::$Cache->delete_value("torrents_details_$GroupID");
  343. G::$DB->set_query_id($QueryID);
  344. }
  345. /**
  346. * Delete a group, called after all of its torrents have been deleted.
  347. * IMPORTANT: Never call this unless you're certain the group is no longer used by any torrents
  348. *
  349. * @param int $GroupID
  350. */
  351. public static function delete_group($GroupID)
  352. {
  353. $QueryID = G::$DB->get_query_id();
  354. Misc::write_log("Group $GroupID automatically deleted (No torrents have this group).");
  355. G::$DB->query("
  356. SELECT CategoryID
  357. FROM torrents_group
  358. WHERE ID = ?", $GroupID);
  359. list($Category) = G::$DB->next_record();
  360. if ($Category == 1) {
  361. G::$Cache->decrement('stats_album_count');
  362. }
  363. G::$Cache->decrement('stats_group_count');
  364. // Collages
  365. G::$DB->query("
  366. SELECT CollageID
  367. FROM collages_torrents
  368. WHERE GroupID = ?", $GroupID);
  369. if (G::$DB->has_results()) {
  370. $CollageIDs = G::$DB->collect('CollageID');
  371. G::$DB->query("
  372. UPDATE collages
  373. SET NumTorrents = NumTorrents - 1
  374. WHERE ID IN (".implode(', ', $CollageIDs).')');
  375. G::$DB->query("
  376. DELETE FROM collages_torrents
  377. WHERE GroupID = ?", $GroupID);
  378. foreach ($CollageIDs as $CollageID) {
  379. G::$Cache->delete_value("collage_$CollageID");
  380. }
  381. G::$Cache->delete_value("torrent_collages_$GroupID");
  382. }
  383. // Artists
  384. // Collect the artist IDs and then wipe the torrents_artist entry
  385. G::$DB->query("
  386. SELECT ArtistID
  387. FROM torrents_artists
  388. WHERE GroupID = ?", $GroupID);
  389. $Artists = G::$DB->collect('ArtistID');
  390. G::$DB->query("
  391. DELETE FROM torrents_artists
  392. WHERE GroupID = ?", $GroupID);
  393. foreach ($Artists as $ArtistID) {
  394. if (empty($ArtistID)) {
  395. continue;
  396. }
  397. // Get a count of how many groups or requests use the artist ID
  398. G::$DB->query("
  399. SELECT COUNT(ag.ArtistID)
  400. FROM artists_group AS ag
  401. LEFT JOIN requests_artists AS ra ON ag.ArtistID = ra.ArtistID
  402. WHERE ra.ArtistID IS NOT NULL
  403. AND ag.ArtistID = ?", $ArtistID);
  404. list($ReqCount) = G::$DB->next_record();
  405. G::$DB->query("
  406. SELECT COUNT(ag.ArtistID)
  407. FROM artists_group AS ag
  408. LEFT JOIN torrents_artists AS ta ON ag.ArtistID = ta.ArtistID
  409. WHERE ta.ArtistID IS NOT NULL
  410. AND ag.ArtistID = ?", $ArtistID);
  411. list($GroupCount) = G::$DB->next_record();
  412. if (($ReqCount + $GroupCount) == 0) {
  413. //The only group to use this artist
  414. Artists::delete_artist($ArtistID);
  415. } else {
  416. //Not the only group, still need to clear cache
  417. G::$Cache->delete_value("artist_groups_$ArtistID");
  418. }
  419. }
  420. // Requests
  421. G::$DB->query("
  422. SELECT ID
  423. FROM requests
  424. WHERE GroupID = ?", $GroupID);
  425. $Requests = G::$DB->collect('ID');
  426. G::$DB->query("
  427. UPDATE requests
  428. SET GroupID = NULL
  429. WHERE GroupID = ?", $GroupID);
  430. foreach ($Requests as $RequestID) {
  431. G::$Cache->delete_value("request_$RequestID");
  432. }
  433. // Comments
  434. Comments::delete_page('torrents', $GroupID);
  435. G::$DB->query("
  436. DELETE FROM torrents_group
  437. WHERE ID = ?", $GroupID);
  438. G::$DB->query("
  439. DELETE FROM torrents_tags
  440. WHERE GroupID = ?", $GroupID);
  441. G::$DB->query("
  442. DELETE FROM bookmarks_torrents
  443. WHERE GroupID = ?", $GroupID);
  444. G::$DB->query("
  445. DELETE FROM wiki_torrents
  446. WHERE PageID = ?", $GroupID);
  447. G::$Cache->delete_value("torrents_details_$GroupID");
  448. G::$Cache->delete_value("torrent_group_$GroupID");
  449. G::$Cache->delete_value("groups_artists_$GroupID");
  450. G::$DB->set_query_id($QueryID);
  451. }
  452. /**
  453. * Update the cache and sphinx delta index to keep everything up-to-date.
  454. *
  455. * @param int $GroupID
  456. */
  457. public static function update_hash($GroupID)
  458. {
  459. $QueryID = G::$DB->get_query_id();
  460. G::$DB->query("
  461. UPDATE torrents_group
  462. SET TagList = (
  463. SELECT REPLACE(GROUP_CONCAT(tags.Name SEPARATOR ' '), '.', '_')
  464. FROM torrents_tags AS t
  465. INNER JOIN tags ON tags.ID = t.TagID
  466. WHERE t.GroupID = ?
  467. GROUP BY t.GroupID
  468. )
  469. WHERE ID = ?", $GroupID, $GroupID);
  470. // Fetch album artists
  471. G::$DB->query("
  472. SELECT GROUP_CONCAT(ag.Name separator ' ')
  473. FROM torrents_artists AS ta
  474. JOIN artists_group AS ag ON ag.ArtistID = ta.ArtistID
  475. WHERE ta.GroupID = ?
  476. GROUP BY ta.GroupID", $GroupID);
  477. if (G::$DB->has_results()) {
  478. list($ArtistName) = G::$DB->next_record(MYSQLI_NUM, false);
  479. } else {
  480. $ArtistName = '';
  481. }
  482. G::$DB->query("
  483. REPLACE INTO sphinx_delta
  484. (ID, GroupID, GroupName, GroupNameRJ, GroupNameJP, TagList, Year, CatalogueNumber, CategoryID, Time,
  485. Size, Snatched, Seeders, Leechers, Censored, Studio, Series, DLsiteID,
  486. FreeTorrent, Media, Container, Codec, Resolution, AudioFormat, Subbing, Language, Description,
  487. FileList, ArtistName)
  488. SELECT
  489. t.ID, g.ID, Name, NameRJ, NameJP, TagList, Year, CatalogueNumber, CategoryID, UNIX_TIMESTAMP(t.Time),
  490. Size, Snatched, Seeders, Leechers, Censored, Studio, Series, DLsiteID,
  491. CAST(FreeTorrent AS CHAR), Media, Container, Codec, Resolution, AudioFormat, Subbing, Language, Description,
  492. REPLACE(REPLACE(FileList, '_', ' '), '/', ' ') AS FileList, ?
  493. FROM torrents AS t
  494. JOIN torrents_group AS g ON g.ID = t.GroupID
  495. WHERE g.ID = ?", $ArtistName, $GroupID);
  496. G::$Cache->delete_value("torrents_details_$GroupID");
  497. G::$Cache->delete_value("torrent_group_$GroupID");
  498. G::$Cache->delete_value("torrent_group_light_$GroupID");
  499. $ArtistInfo = Artists::get_artist($GroupID);
  500. G::$Cache->delete_value("groups_artists_$GroupID");
  501. G::$DB->set_query_id($QueryID);
  502. }
  503. /**
  504. * Regenerate a torrent's file list from its meta data,
  505. * update the database record and clear relevant cache keys
  506. *
  507. * @param int $TorrentID
  508. */
  509. public static function regenerate_filelist($TorrentID)
  510. {
  511. $QueryID = G::$DB->get_query_id();
  512. G::$DB->query("
  513. SELECT GroupID
  514. FROM torrents
  515. WHERE ID = ?", $TorrentID);
  516. if (G::$DB->has_results()) {
  517. list($GroupID) = G::$DB->next_record(MYSQLI_NUM, false);
  518. $Contents = file_get_contents(TORRENT_STORE.$TorrentID.'.torrent');
  519. if (Misc::is_new_torrent($Contents)) {
  520. $Tor = new BencodeTorrent($Contents);
  521. $FilePath = (isset($Tor->Dec['info']['files']) ? Format::make_utf8($Tor->get_name()) : '');
  522. } else {
  523. $Tor = new TORRENT(unserialize(base64_decode($Contents)), true);
  524. $FilePath = (isset($Tor->Val['info']->Val['files']) ? Format::make_utf8($Tor->get_name()) : '');
  525. }
  526. list($TotalSize, $FileList) = $Tor->file_list();
  527. foreach ($FileList as $File) {
  528. $TmpFileList[] = self::filelist_format_file($File);
  529. }
  530. $FileString = implode("\n", $TmpFileList);
  531. G::$DB->query(
  532. "
  533. UPDATE torrents
  534. SET Size = ?, FilePath = ?, FileList = ?
  535. WHERE ID = ?",
  536. $TotalSize,
  537. $FilePath,
  538. $FileString,
  539. $TorrentID
  540. );
  541. G::$Cache->delete_value("torrents_details_$GroupID");
  542. }
  543. G::$DB->set_query_id($QueryID);
  544. }
  545. /**
  546. * Return UTF-8 encoded string to use as file delimiter in torrent file lists
  547. */
  548. public static function filelist_delim()
  549. {
  550. static $FilelistDelimUTF8;
  551. if (isset($FilelistDelimUTF8)) {
  552. return $FilelistDelimUTF8;
  553. }
  554. return $FilelistDelimUTF8 = utf8_encode(chr(self::FILELIST_DELIM));
  555. }
  556. /**
  557. * Create a string that contains file info in a format that's easy to use for Sphinx
  558. *
  559. * @param array $File (File size, File name)
  560. * @return string with the format .EXT sSIZEs NAME DELIMITER
  561. */
  562. public static function filelist_format_file($File)
  563. {
  564. list($Size, $Name) = $File;
  565. $Name = Format::make_utf8(strtr($Name, "\n\r\t", ' '));
  566. $ExtPos = strrpos($Name, '.');
  567. // Should not be $ExtPos !== false. Extensionless files that start with a . should not get extensions
  568. $Ext = ($ExtPos ? trim(substr($Name, $ExtPos + 1)) : '');
  569. return sprintf("%s s%ds %s %s", ".$Ext", $Size, $Name, self::filelist_delim());
  570. }
  571. /**
  572. * Create a string that contains file info in the old format for the API
  573. *
  574. * @param string $File string with the format .EXT sSIZEs NAME DELIMITER
  575. * @return string with the format NAME{{{SIZE}}}
  576. */
  577. public static function filelist_old_format($File)
  578. {
  579. $File = self::filelist_get_file($File);
  580. return $File['name'] . '{{{' . $File['size'] . '}}}';
  581. }
  582. /**
  583. * Translate a formatted file info string into a more useful array structure
  584. *
  585. * @param string $File string with the format .EXT sSIZEs NAME DELIMITER
  586. * @return file info array with the keys 'ext', 'size' and 'name'
  587. */
  588. public static function filelist_get_file($File)
  589. {
  590. // Need this hack because filelists are always display_str()ed
  591. $DelimLen = strlen(display_str(self::filelist_delim())) + 1;
  592. list($FileExt, $Size, $Name) = explode(' ', $File, 3);
  593. if ($Spaces = strspn($Name, ' ')) {
  594. $Name = str_replace(' ', '&nbsp;', substr($Name, 0, $Spaces)) . substr($Name, $Spaces);
  595. }
  596. return array(
  597. 'ext' => $FileExt,
  598. 'size' => substr($Size, 1, -1),
  599. 'name' => substr($Name, 0, -$DelimLen)
  600. );
  601. }
  602. /**
  603. * Format the information about a torrent.
  604. * @param $Data an array a subset of the following keys:
  605. * Format, Encoding, HasLog, LogScore HasCue, Media, Scene, RemasterYear
  606. * RemasterTitle, FreeTorrent, PersonalFL
  607. * @param boolean $ShowMedia if false, Media key will be omitted
  608. * @param boolean $ShowEdition if false, RemasterYear/RemasterTitle will be omitted
  609. */
  610. public static function torrent_info($Data, $ShowMedia = true, $ShowEdition = false, $HTMLy = true)
  611. {
  612. # Main torrent search results info!
  613. $Info = [];
  614. # Platform
  615. if ($ShowMedia && !empty($Data['Media'])) {
  616. $Info[] = '<a class="search_link" href="torrents.php?action=advanced&media='.display_str($Data['Media']).'">'.display_str($Data['Media']).'</a>';
  617. }
  618. # Format
  619. if (!empty($Data['Container'])) {
  620. $Info[] = '<a class="search_link" href="torrents.php?action=advanced&container='.display_str($Data['Container']).'">'.display_str($Data['Container']).'</a>';
  621. }
  622. # Archive
  623. if (!empty($Data['Archive'])) {
  624. # todo: Search on archives, lowest priority
  625. $Info[] = display_str($Data['Archive']);
  626. }
  627. # Resolution
  628. if (!empty($Data['Resolution'])) {
  629. $Info[] = '<a class="search_link" href="torrents.php?action=advanced&resolution='.display_str($Data['Resolution']).'">'.display_str($Data['Resolution']).'</a>';
  630. }
  631. # License
  632. if (!empty($Data['Codec'])) {
  633. $Info[] = '<a class="search_link" href="torrents.php?action=advanced&codec='.display_str($Data['Codec']).'">'.display_str($Data['Codec']).'</a>';
  634. }
  635. # Alignned/Annotated
  636. if ($Data['Censored'] === 1) {
  637. $Info[] = '<a class="search_link" href="torrents.php?action=advanced&censored=1">Aligned</a>';
  638. } else {
  639. $Info[] = '<a class="search_link" href="torrents.php?action=advanced&censored=0">Not Aligned</a>';
  640. }
  641. /*
  642. if (!empty($Data['AudioFormat'])) {
  643. $Info[] = $Data['AudioFormat'];
  644. }
  645. */
  646. /*
  647. if (!empty($Data['Language'])) {
  648. if (!empty($Data['Subber']) && isset($Data['CategoryID']) && ($Data['CategoryID'] == 3 || $Data['CategoryID'] == 4)) {
  649. $Info[] = $Data['Language']." (".$Data['Subber'].")";
  650. } else {
  651. $Info[] = $Data['Language'];
  652. }
  653. }
  654. */
  655. /*
  656. if (!empty($Data['Subbing'])) {
  657. if (!empty($Data['Subber'])) {
  658. if (isset($Data['CategoryID']) && ($Data['CategoryID'] == 2 || $Data['CategoryID'] == 1) && $Data['Subbing'] != "RAW") {
  659. $Info[] = $Data['Subbing'] . " (" . $Data['Subber'] . ")";
  660. }
  661. } else {
  662. $Info[] = $Data['Subbing'];
  663. }
  664. }
  665. */
  666. if ($Data['IsLeeching']) {
  667. $Info[] = $HTMLy ? Format::torrent_label('Leeching', 'important_text') : 'Leeching';
  668. } elseif ($Data['IsSeeding']) {
  669. $Info[] = $HTMLy ? Format::torrent_label('Seeding', 'important_text_alt') : 'Seeding';
  670. } elseif ($Data['IsSnatched']) {
  671. $Info[] = $HTMLy ? Format::torrent_label('Snatched', 'bold') : 'Snatched';
  672. }
  673. if ($Data['FreeTorrent'] === '1') {
  674. if ($Data['FreeLeechType'] === '3') {
  675. if ($Data['ExpiryTime']) {
  676. $Info[] = ($HTMLy ? Format::torrent_label('Freeleech!', 'important_text_alt') : 'Freeleech!') . ($HTMLy ? " <strong>(" : " (") . str_replace(['week','day','hour','min','Just now','s',' '], ['w','d','h','m','0m'], time_diff(max(strtotime($Data['ExpiryTime']), time()), 1, false)) . ($HTMLy ? ")</strong>" : ")");
  677. } else {
  678. $Info[] = $HTMLy ? Format::torrent_label('Freeleech!', 'important_text_alt') : 'Freeleech!';
  679. }
  680. } else {
  681. $Info[] = $HTMLy ? Format::torrent_label('Freeleech!', 'important_text_alt') : 'Freeleech!';
  682. }
  683. }
  684. if ($Data['FreeTorrent'] == '2') {
  685. $Info[] = $HTMLy ? Format::torrent_label('Neutral Leech!', 'bold') : 'Neutral Leech!';
  686. }
  687. if ($Data['PersonalFL']) {
  688. $Info[] = $HTMLy ? Format::torrent_label('Personal Freeleech!', 'important_text_alt') : 'Personal Freeleech!';
  689. }
  690. return implode(' / ', $Info);
  691. }
  692. /**
  693. * Will freeleech / neutral leech / normalise a set of torrents
  694. *
  695. * @param array $TorrentIDs An array of torrent IDs to iterate over
  696. * @param int $FreeNeutral 0 = normal, 1 = fl, 2 = nl
  697. * @param int $FreeLeechType 0 = Unknown, 1 = Staff picks, 2 = Perma-FL (Toolbox, etc.), 3 = Vanity House
  698. */
  699. public static function freeleech_torrents($TorrentIDs, $FreeNeutral = 1, $FreeLeechType = 0, $Announce = true)
  700. {
  701. if (!is_array($TorrentIDs)) {
  702. $TorrentIDs = array($TorrentIDs);
  703. }
  704. $QueryID = G::$DB->get_query_id();
  705. G::$DB->query("
  706. UPDATE torrents
  707. SET FreeTorrent = '$FreeNeutral', FreeLeechType = '$FreeLeechType'
  708. WHERE ID IN (".implode(', ', $TorrentIDs).')');
  709. G::$DB->query('
  710. SELECT ID, GroupID, info_hash
  711. FROM torrents
  712. WHERE ID IN ('.implode(', ', $TorrentIDs).')
  713. ORDER BY GroupID ASC');
  714. $Torrents = G::$DB->to_array(false, MYSQLI_NUM, false);
  715. $GroupIDs = G::$DB->collect('GroupID');
  716. G::$DB->set_query_id($QueryID);
  717. foreach ($Torrents as $Torrent) {
  718. list($TorrentID, $GroupID, $InfoHash) = $Torrent;
  719. Tracker::update_tracker('update_torrent', array('info_hash' => rawurlencode($InfoHash), 'freetorrent' => $FreeNeutral));
  720. G::$Cache->delete_value("torrent_download_$TorrentID");
  721. Misc::write_log((G::$LoggedUser['Username']??'System')." marked torrent $TorrentID freeleech type $FreeLeechType");
  722. Torrents::write_group_log($GroupID, $TorrentID, (G::$LoggedUser['ID']??0), "marked as freeleech type $FreeLeechType", 0);
  723. if ($Announce && ($FreeLeechType === 1 || $FreeLeechType === 3)) {
  724. send_irc('PRIVMSG '.BOT_ANNOUNCE_CHAN.' FREELEECH - '.site_url()."torrents.php?id=$GroupID / ".site_url()."torrents.php?action=download&id=$TorrentID");
  725. }
  726. }
  727. foreach ($GroupIDs as $GroupID) {
  728. Torrents::update_hash($GroupID);
  729. }
  730. }
  731. /**
  732. * Convenience function to allow for passing groups to Torrents::freeleech_torrents()
  733. *
  734. * @param array $GroupIDs the groups in question
  735. * @param int $FreeNeutral see Torrents::freeleech_torrents()
  736. * @param int $FreeLeechType see Torrents::freeleech_torrents()
  737. */
  738. public static function freeleech_groups($GroupIDs, $FreeNeutral = 1, $FreeLeechType = 0)
  739. {
  740. $QueryID = G::$DB->get_query_id();
  741. if (!is_array($GroupIDs)) {
  742. $GroupIDs = [$GroupIDs];
  743. }
  744. G::$DB->query('
  745. SELECT ID
  746. FROM torrents
  747. WHERE GroupID IN ('.implode(', ', $GroupIDs).')');
  748. if (G::$DB->has_results()) {
  749. $TorrentIDs = G::$DB->collect('ID');
  750. Torrents::freeleech_torrents($TorrentIDs, $FreeNeutral, $FreeLeechType);
  751. }
  752. G::$DB->set_query_id($QueryID);
  753. }
  754. /**
  755. * Check if the logged in user has an active freeleech token
  756. *
  757. * @param int $TorrentID
  758. * @return true if an active token exists
  759. */
  760. public static function has_token($TorrentID)
  761. {
  762. if (empty(G::$LoggedUser)) {
  763. return false;
  764. }
  765. static $TokenTorrents;
  766. $UserID = G::$LoggedUser['ID'];
  767. if (!isset($TokenTorrents)) {
  768. $TokenTorrents = G::$Cache->get_value("users_tokens_$UserID");
  769. if ($TokenTorrents === false) {
  770. $QueryID = G::$DB->get_query_id();
  771. G::$DB->query("
  772. SELECT TorrentID
  773. FROM users_freeleeches
  774. WHERE UserID = ?
  775. AND Expired = 0", $UserID);
  776. $TokenTorrents = array_fill_keys(G::$DB->collect('TorrentID', false), true);
  777. G::$DB->set_query_id($QueryID);
  778. G::$Cache->cache_value("users_tokens_$UserID", $TokenTorrents);
  779. }
  780. }
  781. return isset($TokenTorrents[$TorrentID]);
  782. }
  783. /**
  784. * Check if the logged in user can use a freeleech token on this torrent
  785. *
  786. * @param int $Torrent
  787. * @return boolen True if user is allowed to use a token
  788. */
  789. public static function can_use_token($Torrent)
  790. {
  791. if (empty(G::$LoggedUser)) {
  792. return false;
  793. }
  794. return (G::$LoggedUser['FLTokens'] > 0
  795. && $Torrent['Size'] <= 10737418240
  796. && !$Torrent['PersonalFL']
  797. && empty($Torrent['FreeTorrent'])
  798. && G::$LoggedUser['CanLeech'] == '1');
  799. }
  800. /**
  801. * Build snatchlists and check if a torrent has been snatched
  802. * if a user has the 'ShowSnatched' option enabled
  803. * @param int $TorrentID
  804. * @return bool
  805. */
  806. public static function has_snatched($TorrentID)
  807. {
  808. if (empty(G::$LoggedUser) || !isset(G::$LoggedUser['ShowSnatched']) || !G::$LoggedUser['ShowSnatched']) {
  809. return false;
  810. }
  811. $UserID = G::$LoggedUser['ID'];
  812. $Buckets = 64;
  813. $LastBucket = $Buckets - 1;
  814. $BucketID = $TorrentID & $LastBucket;
  815. static $SnatchedTorrents = [], $UpdateTime = [];
  816. if (empty($SnatchedTorrents)) {
  817. $SnatchedTorrents = array_fill(0, $Buckets, false);
  818. $UpdateTime = G::$Cache->get_value("users_snatched_{$UserID}_time");
  819. if ($UpdateTime === false) {
  820. $UpdateTime = array(
  821. 'last' => 0,
  822. 'next' => 0);
  823. }
  824. } elseif (isset($SnatchedTorrents[$BucketID][$TorrentID])) {
  825. return true;
  826. }
  827. // Torrent was not found in the previously inspected snatch lists
  828. $CurSnatchedTorrents =& $SnatchedTorrents[$BucketID];
  829. if ($CurSnatchedTorrents === false) {
  830. $CurTime = time();
  831. // This bucket hasn't been checked before
  832. $CurSnatchedTorrents = G::$Cache->get_value("users_snatched_{$UserID}_$BucketID", true);
  833. if ($CurSnatchedTorrents === false || $CurTime > $UpdateTime['next']) {
  834. $Updated = [];
  835. $QueryID = G::$DB->get_query_id();
  836. if ($CurSnatchedTorrents === false || $UpdateTime['last'] == 0) {
  837. for ($i = 0; $i < $Buckets; $i++) {
  838. $SnatchedTorrents[$i] = [];
  839. }
  840. // Not found in cache. Since we don't have a suitable index, it's faster to update everything
  841. G::$DB->query("
  842. SELECT fid
  843. FROM xbt_snatched
  844. WHERE uid = ?", $UserID);
  845. while (list($ID) = G::$DB->next_record(MYSQLI_NUM, false)) {
  846. $SnatchedTorrents[$ID & $LastBucket][(int)$ID] = true;
  847. }
  848. $Updated = array_fill(0, $Buckets, true);
  849. } elseif (isset($CurSnatchedTorrents[$TorrentID])) {
  850. // Old cache, but torrent is snatched, so no need to update
  851. return true;
  852. } else {
  853. // Old cache, check if torrent has been snatched recently
  854. G::$DB->query("
  855. SELECT fid
  856. FROM xbt_snatched
  857. WHERE uid = ?
  858. AND tstamp >= ?", $UserID, $UpdateTime['last']);
  859. while (list($ID) = G::$DB->next_record(MYSQLI_NUM, false)) {
  860. $CurBucketID = $ID & $LastBucket;
  861. if ($SnatchedTorrents[$CurBucketID] === false) {
  862. $SnatchedTorrents[$CurBucketID] = G::$Cache->get_value("users_snatched_{$UserID}_$CurBucketID", true);
  863. if ($SnatchedTorrents[$CurBucketID] === false) {
  864. $SnatchedTorrents[$CurBucketID] = [];
  865. }
  866. }
  867. $SnatchedTorrents[$CurBucketID][(int)$ID] = true;
  868. $Updated[$CurBucketID] = true;
  869. }
  870. }
  871. G::$DB->set_query_id($QueryID);
  872. for ($i = 0; $i < $Buckets; $i++) {
  873. if (isset($Updated[$i])) {
  874. G::$Cache->cache_value("users_snatched_{$UserID}_$i", $SnatchedTorrents[$i], 0);
  875. }
  876. }
  877. $UpdateTime['last'] = $CurTime;
  878. $UpdateTime['next'] = $CurTime + self::SNATCHED_UPDATE_INTERVAL;
  879. G::$Cache->cache_value("users_snatched_{$UserID}_time", $UpdateTime, 0);
  880. }
  881. }
  882. return isset($CurSnatchedTorrents[$TorrentID]);
  883. }
  884. public static function is_seeding($TorrentID)
  885. {
  886. if (empty(G::$LoggedUser) || !isset(G::$LoggedUser['ShowSnatched']) || !G::$LoggedUser['ShowSnatched']) {
  887. return false;
  888. }
  889. $UserID = G::$LoggedUser['ID'];
  890. $Buckets = 64;
  891. $LastBucket = $Buckets - 1;
  892. $BucketID = $TorrentID & $LastBucket;
  893. static $SeedingTorrents = [], $UpdateTime = [];
  894. if (empty($SeedingTorrents)) {
  895. $SeedingTorrents = array_fill(0, $Buckets, false);
  896. $UpdateTime = G::$Cache->get_value("users_seeding_{$UserID}_time");
  897. if ($UpdateTime === false) {
  898. $UpdateTime = array(
  899. 'last' => 0,
  900. 'next' => 0);
  901. }
  902. } elseif (isset($SeedingTorrents[$BucketID][$TorrentID])) {
  903. return true;
  904. }
  905. // Torrent was not found in the previously inspected seeding lists
  906. $CurSeedingTorrents =& $SeedingTorrents[$BucketID];
  907. if ($CurSeedingTorrents === false) {
  908. $CurTime = time();
  909. // This bucket hasn't been checked before
  910. $CurSeedingTorrents = G::$Cache->get_value("users_seeding_{$UserID}_$BucketID", true);
  911. if ($CurSeedingTorrents === false || $CurTime > $UpdateTime['next']) {
  912. $Updated = [];
  913. $QueryID = G::$DB->get_query_id();
  914. if ($CurSeedingTorrents === false || $UpdateTime['last'] == 0) {
  915. for ($i = 0; $i < $Buckets; $i++) {
  916. $SeedingTorrents[$i] = [];
  917. }
  918. // Not found in cache. Since we don't have a suitable index, it's faster to update everything
  919. G::$DB->query("
  920. SELECT fid
  921. FROM xbt_files_users
  922. WHERE uid = ?
  923. AND active = 1
  924. AND Remaining = 0", $UserID);
  925. while (list($ID) = G::$DB->next_record(MYSQLI_NUM, false)) {
  926. $SeedingTorrents[$ID & $LastBucket][(int)$ID] = true;
  927. }
  928. $Updated = array_fill(0, $Buckets, true);
  929. } elseif (isset($CurSeedingTorrents[$TorrentID])) {
  930. // Old cache, but torrent is seeding, so no need to update
  931. return true;
  932. } else {
  933. // Old cache, check if torrent has been seeding recently
  934. G::$DB->query("
  935. SELECT fid
  936. FROM xbt_files_users
  937. WHERE uid = ?
  938. AND active = 1
  939. AND Remaining = 0
  940. AND mtime >= ?", $UserID, $UpdateTime['last']);
  941. while (list($ID) = G::$DB->next_record(MYSQLI_NUM, false)) {
  942. $CurBucketID = $ID & $LastBucket;
  943. if ($SeedingTorrents[$CurBucketID] === false) {
  944. $SeedingTorrents[$CurBucketID] = G::$Cache->get_value("users_seeding_{$UserID}_$CurBucketID", true);
  945. if ($SeedingTorrents[$CurBucketID] === false) {
  946. $SeedingTorrents[$CurBucketID] = [];
  947. }
  948. }
  949. $SeedingTorrents[$CurBucketID][(int)$ID] = true;
  950. $Updated[$CurBucketID] = true;
  951. }
  952. }
  953. G::$DB->set_query_id($QueryID);
  954. for ($i = 0; $i < $Buckets; $i++) {
  955. if (isset($Updated[$i])) {
  956. G::$Cache->cache_value("users_seeding_{$UserID}_$i", $SeedingTorrents[$i], 3600);
  957. }
  958. }
  959. $UpdateTime['last'] = $CurTime;
  960. $UpdateTime['next'] = $CurTime + self::SNATCHED_UPDATE_INTERVAL;
  961. G::$Cache->cache_value("users_seeding_{$UserID}_time", $UpdateTime, 3600);
  962. }
  963. }
  964. return isset($CurSeedingTorrents[$TorrentID]);
  965. }
  966. public static function is_leeching($TorrentID)
  967. {
  968. if (empty(G::$LoggedUser) || !isset(G::$LoggedUser['ShowSnatched']) || !G::$LoggedUser['ShowSnatched']) {
  969. return false;
  970. }
  971. $UserID = G::$LoggedUser['ID'];
  972. $Buckets = 64;
  973. $LastBucket = $Buckets - 1;
  974. $BucketID = $TorrentID & $LastBucket;
  975. static $LeechingTorrents = [], $UpdateTime = [];
  976. if (empty($LeechingTorrents)) {
  977. $LeechingTorrents = array_fill(0, $Buckets, false);
  978. $UpdateTime = G::$Cache->get_value("users_leeching_{$UserID}_time");
  979. if ($UpdateTime === false) {
  980. $UpdateTime = array(
  981. 'last' => 0,
  982. 'next' => 0);
  983. }
  984. } elseif (isset($LeechingTorrents[$BucketID][$TorrentID])) {
  985. return true;
  986. }
  987. // Torrent was not found in the previously inspected snatch lists
  988. $CurLeechingTorrents =& $LeechingTorrents[$BucketID];
  989. if ($CurLeechingTorrents === false) {
  990. $CurTime = time();
  991. // This bucket hasn't been checked before
  992. $CurLeechingTorrents = G::$Cache->get_value("users_leeching_{$UserID}_$BucketID", true);
  993. if ($CurLeechingTorrents === false || $CurTime > $UpdateTime['next']) {
  994. $Updated = [];
  995. $QueryID = G::$DB->get_query_id();
  996. if ($CurLeechingTorrents === false || $UpdateTime['last'] == 0) {
  997. for ($i = 0; $i < $Buckets; $i++) {
  998. $LeechingTorrents[$i] = [];
  999. }
  1000. // Not found in cache. Since we don't have a suitable index, it's faster to update everything
  1001. G::$DB->query("
  1002. SELECT fid
  1003. FROM xbt_files_users
  1004. WHERE uid = ?
  1005. AND active = 1
  1006. AND Remaining > 0", $UserID);
  1007. while (list($ID) = G::$DB->next_record(MYSQLI_NUM, false)) {
  1008. $LeechingTorrents[$ID & $LastBucket][(int)$ID] = true;
  1009. }
  1010. $Updated = array_fill(0, $Buckets, true);
  1011. } elseif (isset($CurLeechingTorrents[$TorrentID])) {
  1012. // Old cache, but torrent is leeching, so no need to update
  1013. return true;
  1014. } else {
  1015. // Old cache, check if torrent has been leeching recently
  1016. G::$DB->query("
  1017. SELECT fid
  1018. FROM xbt_files_users
  1019. WHERE uid = ?
  1020. AND active = 1
  1021. AND Remaining > 0
  1022. AND mtime >= ?", $UserID, $UpdateTime['last']);
  1023. while (list($ID) = G::$DB->next_record(MYSQLI_NUM, false)) {
  1024. $CurBucketID = $ID & $LastBucket;
  1025. if ($LeechingTorrents[$CurBucketID] === false) {
  1026. $LeechingTorrents[$CurBucketID] = G::$Cache->get_value("users_leeching_{$UserID}_$CurBucketID", true);
  1027. if ($LeechingTorrents[$CurBucketID] === false) {
  1028. $LeechingTorrents[$CurBucketID] = [];
  1029. }
  1030. }
  1031. $LeechingTorrents[$CurBucketID][(int)$ID] = true;
  1032. $Updated[$CurBucketID] = true;
  1033. }
  1034. }
  1035. G::$DB->set_query_id($QueryID);
  1036. for ($i = 0; $i < $Buckets; $i++) {
  1037. if (isset($Updated[$i])) {
  1038. G::$Cache->cache_value("users_leeching_{$UserID}_$i", $LeechingTorrents[$i], 3600);
  1039. }
  1040. }
  1041. $UpdateTime['last'] = $CurTime;
  1042. $UpdateTime['next'] = $CurTime + self::SNATCHED_UPDATE_INTERVAL;
  1043. G::$Cache->cache_value("users_leeching_{$UserID}_time", $UpdateTime, 3600);
  1044. }
  1045. }
  1046. return isset($CurLeechingTorrents[$TorrentID]);
  1047. }
  1048. /*
  1049. public static function is_seeding_or_leeching($TorrentID) {
  1050. if (empty(G::$LoggedUser))
  1051. return false;
  1052. $UserID = G::$LoggedUser['ID'];
  1053. $Result = array("IsSeeding" => false, "IsLeeching" => false);
  1054. $QueryID = G::$DB->get_query_id();
  1055. G::$DB->query("
  1056. SELECT Remaining
  1057. FROM xbt_files_users
  1058. WHERE fid = $TorrentID
  1059. AND uid = $UserID
  1060. AND active = 1");
  1061. if (G::$DB->has_results()) {
  1062. while (($Row = G::$DB->next_record(MYSQLI_ASSOC, true)) && !($Result['IsSeeding'] && $Result['IsLeeching'])) {
  1063. if ($Row['Remaining'] == 0)
  1064. $Result['IsSeeding'] = true;
  1065. if ($Row['Remaining'] > 0)
  1066. $Result['IsLeeching'] = true;
  1067. }
  1068. }
  1069. G::$DB->set_query_id($QueryID);
  1070. return $Result;
  1071. }
  1072. */
  1073. /**
  1074. * Change the schedule for when the next update to a user's cached snatch list should be performed.
  1075. * By default, the change will only be made if the new update would happen sooner than the current
  1076. * @param int $Time Seconds until the next update
  1077. * @param bool $Force Whether to accept changes that would push back the update
  1078. */
  1079. public static function set_snatch_update_time($UserID, $Time, $Force = false)
  1080. {
  1081. if (!$UpdateTime = G::$Cache->get_value("users_snatched_{$UserID}_time")) {
  1082. return;
  1083. }
  1084. $NextTime = time() + $Time;
  1085. if ($Force || $NextTime < $UpdateTime['next']) {
  1086. // Skip if the change would delay the next update
  1087. $UpdateTime['next'] = $NextTime;
  1088. G::$Cache->cache_value("users_snatched_{$UserID}_time", $UpdateTime, 0);
  1089. }
  1090. }
  1091. // Some constants for self::display_string's $Mode parameter
  1092. const DISPLAYSTRING_HTML = 1; // Whether or not to use HTML for the output (e.g. VH tooltip)
  1093. const DISPLAYSTRING_ARTISTS = 2; // Whether or not to display artists
  1094. const DISPLAYSTRING_YEAR = 4; // Whether or not to display the group's year
  1095. const DISPLAYSTRING_VH = 8; // Whether or not to display the VH flag
  1096. const DISPLAYSTRING_RELEASETYPE = 16; // Whether or not to display the release type
  1097. const DISPLAYSTRING_LINKED = 33; // Whether or not to link artists and the group
  1098. // The constant for linking is 32, but because linking only works with HTML, this constant is defined as 32|1 = 33, i.e. LINKED also includes HTML
  1099. // Keep this in mind when defining presets below!
  1100. // Presets to facilitate the use of $Mode
  1101. const DISPLAYSTRING_DEFAULT = 63; // HTML|ARTISTS|YEAR|VH|RELEASETYPE|LINKED = 63
  1102. const DISPLAYSTRING_SHORT = 6; // Very simple format, only artists and year, no linking (e.g. for forum thread titles)
  1103. /**
  1104. * Return the display string for a given torrent group $GroupID.
  1105. * @param int $GroupID
  1106. * @return string
  1107. */
  1108. public static function display_string($GroupID, $Mode = self::DISPLAYSTRING_DEFAULT)
  1109. {
  1110. global $ReleaseTypes; // I hate this
  1111. $GroupInfo = self::get_groups(array($GroupID), true, true, false)[$GroupID];
  1112. $ExtendedArtists = $GroupInfo['ExtendedArtists'];
  1113. if ($Mode & self::DISPLAYSTRING_ARTISTS) {
  1114. if (!empty($ExtendedArtists[1])
  1115. || !empty($ExtendedArtists[4])
  1116. || !empty($ExtendedArtists[5])
  1117. || !empty($ExtendedArtists[6])
  1118. ) {
  1119. unset($ExtendedArtists[2], $ExtendedArtists[3]);
  1120. $DisplayName = Artists::display_artists($ExtendedArtists, ($Mode & self::DISPLAYSTRING_LINKED));
  1121. } else {
  1122. $DisplayName = '';
  1123. }
  1124. }
  1125. if ($Mode & self::DISPLAYSTRING_LINKED) {
  1126. $DisplayName .= "<a href=\"torrents.php?id=$GroupID\" class=\"tooltip\" title=\"View torrent group\" dir=\"ltr\">$GroupInfo[Name]</a>";
  1127. } else {
  1128. $DisplayName .= $GroupInfo['Name'];
  1129. }
  1130. if (($Mode & self::DISPLAYSTRING_YEAR) && $GroupInfo['Year'] > 0) {
  1131. $DisplayName .= " [$GroupInfo[Year]]";
  1132. }
  1133. if (($Mode & self::DISPLAYSTRING_RELEASETYPE) && $GroupInfo['ReleaseType'] > 0) {
  1134. $DisplayName .= ' ['.$ReleaseTypes[$GroupInfo['ReleaseType']].']';
  1135. }
  1136. return $DisplayName;
  1137. }
  1138. public static function edition_string(array $Torrent, array $Group)
  1139. {
  1140. $AddExtra = ' / ';
  1141. $EditionName = 'Original Release';
  1142. $EditionName .= $AddExtra . display_str($Torrent['Media']);
  1143. return $EditionName;
  1144. }
  1145. // Used to get reports info on a unison cache in both browsing pages and torrent pages.
  1146. public static function get_reports($TorrentID)
  1147. {
  1148. $Reports = G::$Cache->get_value("reports_torrent_$TorrentID");
  1149. if ($Reports === false) {
  1150. $QueryID = G::$DB->get_query_id();
  1151. G::$DB->query("
  1152. SELECT
  1153. ID,
  1154. ReporterID,
  1155. Type,
  1156. UserComment,
  1157. ReportedTime
  1158. FROM reportsv2
  1159. WHERE TorrentID = ?
  1160. AND Status != 'Resolved'", $TorrentID);
  1161. $Reports = G::$DB->to_array(false, MYSQLI_ASSOC, false);
  1162. G::$DB->set_query_id($QueryID);
  1163. G::$Cache->cache_value("reports_torrent_$TorrentID", $Reports, 0);
  1164. }
  1165. if (!check_perms('admin_reports')) {
  1166. $Return = [];
  1167. foreach ($Reports as $Report) {
  1168. if ($Report['Type'] !== 'edited') {
  1169. $Return[] = $Report;
  1170. }
  1171. }
  1172. return $Return;
  1173. }
  1174. return $Reports;
  1175. }
  1176. }