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

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