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 48KB

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