From 3da718ad8c7c8e15e91b5d9e9b6616b4a8756477 Mon Sep 17 00:00:00 2001 From: Spine Date: Sun, 16 May 2021 15:53:32 +0000 Subject: [PATCH] Restructure Gazelle\Manager\Torrent into four classes --- .docker/mysql/mysqld_sql_mode.cnf | 2 +- app/Bookmark.php | 4 +- app/Collector.php | 9 +- app/Contest/AbstractContest.php | 12 +- app/Json/Torrent.php | 142 ++- app/Manager/Better.php | 2 +- app/Manager/TGroup.php | 148 +++ app/Manager/Torrent.php | 1210 +-------------------- app/Manager/User.php | 16 + app/Seedbox.php | 5 +- app/TGroup.php | 461 ++++++++ app/Torrent.php | 606 +++++++++++ app/Torrent/Reaper.php | 9 +- app/Tracker.php | 3 + app/User.php | 5 +- boris | 1 + classes/config.template.php | 2 + classes/torrents.class.php | 2 + classes/util.php | 3 +- gazelle.php | 3 +- sections/ajax/riplog.php | 2 +- sections/ajax/torrent.php | 6 +- sections/ajax/torrentgroup.php | 25 +- sections/ajax/torrentgroupalbumart.php | 6 +- sections/apply/apply.php | 1 - sections/collages/torrent_collage.php | 7 +- sections/index/private.php | 8 +- sections/logchecker/take_upload.php | 2 +- sections/reportsv2/report.php | 20 +- sections/reportsv2/takeresolve.php | 4 +- sections/tools/sandboxes/notification.php | 8 +- sections/torrents/browse.php | 21 +- sections/torrents/delete.php | 8 +- sections/torrents/delete_log.php | 22 +- sections/torrents/download.php | 6 +- sections/torrents/history.php | 3 +- sections/torrents/peerlist.php | 14 +- sections/torrents/remove_logs.php | 2 +- sections/torrents/rescore_log.php | 22 +- sections/torrents/take_edit_log.php | 24 +- sections/torrents/takedelete.php | 27 +- sections/upload/upload_handle.php | 6 +- sections/user/2fa/configure.php | 1 - sections/userhistory/token_history.php | 6 +- 44 files changed, 1448 insertions(+), 1448 deletions(-) create mode 100644 app/Manager/TGroup.php create mode 100644 app/TGroup.php diff --git a/.docker/mysql/mysqld_sql_mode.cnf b/.docker/mysql/mysqld_sql_mode.cnf index 6166d7bdb..b308fbada 100644 --- a/.docker/mysql/mysqld_sql_mode.cnf +++ b/.docker/mysql/mysqld_sql_mode.cnf @@ -1,5 +1,5 @@ [mysqld] -sql_mode = ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION +sql_mode = ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION,NO_ZERO_DATE userstat = on [client] diff --git a/app/Bookmark.php b/app/Bookmark.php index c00471232..2e7ca4d01 100644 --- a/app/Bookmark.php +++ b/app/Bookmark.php @@ -167,7 +167,9 @@ public function create(int $userId, string $type, int $id) { // RSS feed stuff $Feed = new \Feed; - [$group, $list] = (new Manager\Torrent)->groupInfo($id); + $tgroup = (new Manager\Torrent)->findById($id); + $group = $tgroup->info(); + $list = $tgroup->torrentList(); $labelMan = new Manager\TorrentLabel; $userMan = new Manager\User; foreach ($list as $torrent) { diff --git a/app/Collector.php b/app/Collector.php index 04986de3e..377a66e51 100644 --- a/app/Collector.php +++ b/app/Collector.php @@ -206,13 +206,18 @@ public function add(array $info, $folderName = null) { $this->skip($info); return; } - $contents = $this->torMan->torrentBody($info['TorrentID'], $this->user->announceUrl()); + $torrent = $this->torMan->findById($info['TorrentID']); + if (is_null($torrent)) { + $this->fail($info); + return; + } + $contents = $torrent->torrentBody($this->user->announceUrl()); if ($contents === '') { $this->fail($info); return; } $folder = is_null($folderName) ? '' : (safeFilename($folderName) . '/'); - $name = $this->torMan->torrentFilename($info, false, MAX_PATH_LEN - strlen($folder)); + $name = $torrent->torrentFilename(false, MAX_PATH_LEN - strlen($folder)); $this->zip->addFile("$folder$name", $contents); $this->totalAdded++; diff --git a/app/Contest/AbstractContest.php b/app/Contest/AbstractContest.php index e9fad89be..3bec01db2 100644 --- a/app/Contest/AbstractContest.php +++ b/app/Contest/AbstractContest.php @@ -29,17 +29,15 @@ public function leaderboard(int $limit, int $offset): array { $leaderboard = $this->db->to_array(false, MYSQLI_ASSOC); $leaderboardCount = count($leaderboard); for ($i = 0; $i < $leaderboardCount; $i++) { - [$group, $torrent] = $torMan - ->setTorrentId($leaderboard[$i]['last_entry_id']) - ->setGroupId($leaderboard[$i]['group_id']) - ->torrentInfo(); + $torrent = $torMan->findById($leaderboard[$i]['last_entry_id']); + $group = $torrent->group(); $leaderboard[$i]['last_entry_link'] = sprintf( '%s - %s - %s', - $torMan->artistHtml(), + $group->artistHtml(), $leaderboard[$i]['group_id'], $leaderboard[$i]['last_entry_id'], - $group['Name'], - $labelMan->load($torrent)->label() + $group->name(), + $labelMan->load($torrent->info())->label() ); } $this->cache->cache_value($key, $leaderboard, 3600); diff --git a/app/Json/Torrent.php b/app/Json/Torrent.php index ad692c56c..3a61055cd 100644 --- a/app/Json/Torrent.php +++ b/app/Json/Torrent.php @@ -3,17 +3,16 @@ namespace Gazelle\Json; class Torrent extends \Gazelle\Json { - protected $id; - protected $infohash; + protected $torrent; protected $userId; - protected $showSnatched; + protected $showSnatched = false; public function __construct() { parent::__construct(); $this->setMode(JSON_INVALID_UTF8_SUBSTITUTE | JSON_THROW_ON_ERROR); } - public function setViewer(int $userId) { + public function setViewerId(int $userId) { $this->userId = $userId; return $this; } @@ -23,29 +22,21 @@ public function setShowSnatched(int $showSnatched) { return $this; } - public function setId(int $id) { - if (!$id) { + public function findById(int $id) { + $this->torrent = (new \Gazelle\Manager\Torrent)->findById($id); + if (!$this->torrent) { $this->failure("bad id parameter"); return null; } - $this->id = $id; - $this->infohash = null; return $this; } - public function setIdFromHash(string $hash) { - $torMan = new \Gazelle\Manager\Torrent; - if (!$torMan->isValidHash($hash)) { + public function findByInfohash(string $hash) { + $this->torrent = (new \Gazelle\Manager\Torrent)->findByInfohash($hash); + if (!$this->torrent) { $this->failure("bad hash parameter"); return null; - } else { - $this->id = $torMan->hashToTorrentId($hash); - if (!$this->id) { - $this->failure("bad hash parameter"); - return null; - } } - $this->infohash = $hash; return $this; } @@ -55,88 +46,77 @@ public function payload(): ?array { return null; } - $torMan = new \Gazelle\Manager\Torrent; - [$details, $torrent] = $torMan - ->setTorrentId($this->id) - ->setViewer($this->userId) - ->setShowSnatched($this->showSnatched ?? 0) - ->showFallbackImage(false) - ->torrentInfo(); - if (!$details) { - $this->failure("bad id parameter"); - return null; - } - $groupID = $details['ID']; + $this->torrent->setViewerId($this->userId)->setShowSnatched($this->showSnatched); + $info = $this->torrent->info(); + $group = $this->torrent->group()->showFallbackImage(false)->info(); // TODO: implement as a Gazelle class global $Categories; - $categoryName = ($details['CategoryID'] == 0) ? "Unknown" : $Categories[$details['CategoryID'] - 1]; + $categoryName = ($group['CategoryID'] == 0) ? "Unknown" : $Categories[$group['CategoryID'] - 1]; // Convert file list back to the old format - $fileList = explode("\n", $torrent['FileList']); + $torMan = new \Gazelle\Manager\Torrent; + $fileList = explode("\n", $info['FileList']); foreach ($fileList as &$file) { $file = $torMan->apiFilename($file); } unset($file); - $uploader = (new \Gazelle\Manager\User)->findById($torrent['UserID']); - $username = $uploader ? $uploader->username() : ''; - return [ 'group' => [ - 'wikiBody' => \Text::full_format($details['WikiBody']), - 'wikiBBcode' => $details['WikiBody'], - 'wikiImage' => $details['WikiImage'], - 'id' => $details['ID'], - 'name' => $details['Name'], - 'year' => $details['Year'], - 'recordLabel' => $details['RecordLabel'] ?? '', - 'catalogueNumber' => $details['CatalogueNumber'] ?? '', - 'releaseType' => $details['ReleaseType'] ?? '', - 'releaseTypeName' => (new \Gazelle\ReleaseType)->findNameById($details['ReleaseType']), - 'categoryId' => $details['CategoryID'], + 'wikiBody' => \Text::full_format($group['WikiBody']), + 'wikiBBcode' => $group['WikiBody'], + 'wikiImage' => $group['WikiImage'], + 'id' => $group['ID'], + 'name' => $group['Name'], + 'year' => $group['Year'], + 'recordLabel' => $group['RecordLabel'] ?? '', + 'catalogueNumber' => $group['CatalogueNumber'] ?? '', + 'releaseType' => $group['ReleaseType'] ?? '', + 'releaseTypeName' => (new \Gazelle\ReleaseType)->findNameById($group['ReleaseType']), + 'categoryId' => $group['CategoryID'], 'categoryName' => $categoryName, - 'time' => $details['Time'], - 'vanityHouse' => $details['VanityHouse'], - 'isBookmarked' => (new \Gazelle\Bookmark)->isTorrentBookmarked($this->userId, $groupID), - 'tags' => explode('|', $details['tagNames']), + 'time' => $group['Time'], + 'vanityHouse' => $group['VanityHouse'], + 'isBookmarked' => (new \Gazelle\Bookmark)->isTorrentBookmarked($this->userId, $group['ID']), + 'tags' => explode('|', $group['tagNames']), 'musicInfo' => ($categoryName != "Music") - ? null : \Artists::get_artist_by_type($groupID), + ? null : \Artists::get_artist_by_type($group['ID']), ], 'torrent' => array_merge( - !is_null($this->infohash) || $torrent['UserID'] == $this->userId - ? [ 'infoHash' => $torrent['InfoHash'] ] + !is_null($this->torrent->infohash()) || $this->torrent->uploader()->id() == $this->userId + ? [ 'infoHash' => $this->torrent->infohash() ] : [], [ - 'id' => $torrent['ID'], - 'media' => $torrent['Media'], - 'format' => $torrent['Format'], - 'encoding' => $torrent['Encoding'], - 'remastered' => $torrent['Remastered'] == 1, - 'remasterYear' => (int)$torrent['RemasterYear'], - 'remasterTitle' => $torrent['RemasterTitle'] ?? '', - 'remasterRecordLabel' => $torrent['RemasterRecordLabel'] ?? '', - 'remasterCatalogueNumber' => $torrent['RemasterCatalogueNumber'] ?? '', - 'scene' => $torrent['Scene'], - 'hasLog' => $torrent['HasLog'], - 'hasCue' => $torrent['HasCue'], - 'logScore' => $torrent['LogScore'], - 'logChecksum' => $torrent['LogChecksum'], - 'logCount' => $torrent['LogCount'], - 'ripLogIds' => $torrent['ripLogIds'], - 'fileCount' => $torrent['FileCount'], - 'size' => $torrent['Size'], - 'seeders' => $torrent['Seeders'], - 'leechers' => $torrent['Leechers'], - 'snatched' => $torrent['Snatched'], - 'freeTorrent' => $torrent['FreeTorrent'], - 'reported' => count(\Torrents::get_reports($this->id)) > 0, - 'time' => $torrent['Time'], - 'description' => $torrent['Description'], + 'id' => $this->torrent->id(), + 'media' => $info['Media'], + 'format' => $info['Format'], + 'encoding' => $info['Encoding'], + 'remastered' => $this->torrent->isRemastered(), + 'remasterYear' => $info['RemasterYear'], + 'remasterTitle' => $info['RemasterTitle'], + 'remasterRecordLabel' => $info['RemasterRecordLabel'], + 'remasterCatalogueNumber' => $info['RemasterCatalogueNumber'], + 'scene' => $info['Scene'], + 'hasLog' => $info['HasLog'], + 'hasCue' => $info['HasCue'], + 'logScore' => $info['LogScore'], + 'logChecksum' => $info['LogChecksum'], + 'logCount' => $info['LogCount'], + 'ripLogIds' => $info['ripLogIds'], + 'fileCount' => $info['FileCount'], + 'size' => $info['Size'], + 'seeders' => $info['Seeders'], + 'leechers' => $info['Leechers'], + 'snatched' => $info['Snatched'], + 'freeTorrent' => $info['FreeTorrent'], + 'reported' => count(\Torrents::get_reports($this->torrent->id())) > 0, + 'time' => $info['Time'], + 'description' => $info['Description'], 'fileList' => implode('|||', $fileList), - 'filePath' => $torrent['FilePath'], - 'userId' => $torrent['UserID'], - 'username' => $username, + 'filePath' => $info['FilePath'], + 'userId' => $info['UserID'], + 'username' => $this->torrent->uploader()->username(), ] ), ]; diff --git a/app/Manager/Better.php b/app/Manager/Better.php index eb15f1d83..b2034dc09 100644 --- a/app/Manager/Better.php +++ b/app/Manager/Better.php @@ -23,7 +23,7 @@ public function removeAttribute(string $type, int $id) { ', $table), $id ); - $this->cache->delete_value('torrents_details_' . (new \Gazelle\Manager\Torrent())->idToGroupId($id)); + $this->cache->delete_value('torrents_details_' . (new \Gazelle\Manager\Torrent())->findById($id)->groupId()); } public function missing(string $type, string $filter, string $search, int $limit, int $offset, int $userId) { diff --git a/app/Manager/TGroup.php b/app/Manager/TGroup.php new file mode 100644 index 000000000..11697ba35 --- /dev/null +++ b/app/Manager/TGroup.php @@ -0,0 +1,148 @@ +db->scalar(" + SELECT ID FROM torrents_group WHERE ID = ? + ", $groupId + ); + return $id ? new \Gazelle\TGroup($id) : null; + } + + /** + * Map a torrenthash to a group id + * @param string $hash + * @return int The group id if found, otherwise null + */ + public function findByTorrentInfohash(string $hash) { + $id = $this->db->scalar(" + SELECT GroupID FROM torrents WHERE info_hash = UNHEX(?) + ", $hash + ); + return $id ? new \Gazelle\TGroup($id) : null; + } + + /** + * Return the N most recent lossless uploads + * Note that if both a Lossless and 24bit Lossless are uploaded at the same time, + * only one entry will be returned, to ensure that the result is comprised of N + * different groups. Uploads of paranoid users are excluded. Uploads without + * cover art are excluded. + * + * @param int $limit + * @return array of [imageUrl, groupId, torrentId, uploadDate, username, paranoia] + */ + public function latestUploads(int $limit) { + if (!($latest = $this->cache->get_value(self::CACHE_KEY_LATEST_UPLOADS . $limit))) { + $this->db->prepared_query(" + SELECT tg.WikiImage AS imageUrl, + R.GroupID AS groupId, + R.torrentId, + R.uploadDate, + um.Username AS username, + um.Paranoia AS paranoia, + group_concat(tag.Name ORDER BY tag.Name SEPARATOR ', ') AS tags + FROM ( + SELECT t.GroupID, + max(t.ID) AS torrentId, + max(t.Time) AS uploadDate + FROM torrents t + INNER JOIN torrents_group tg ON (tg.ID = t.GroupID) + WHERE t.Time > now() - INTERVAL 3 DAY + AND t.Encoding IN ('Lossless', '24bit Lossless') + AND tg.WikiImage != '' + AND NOT EXISTS ( + SELECT 1 + FROM torrents_tags ttex + WHERE t.GroupID = ttex.GroupID + AND ttex.TagID IN (" . placeholders(HOMEPAGE_TAG_IGNORE) . ") + ) + GROUP BY t.GroupID + ) R + INNER JOIN torrents_group tg ON (tg.ID = R.groupId) + INNER JOIN torrents_tags tt USING (GroupID) + INNER JOIN tags tag ON (tag.ID = tt.TagID) + INNER JOIN torrents t ON (t.ID = R.torrentId) + INNER JOIN users_main um ON (um.ID = t.UserID) + GROUP BY R.GroupID + ORDER BY R.uploadDate DESC + ", ...HOMEPAGE_TAG_IGNORE + ); + $latest = []; + while (count($latest) < $limit) { + $row = $this->db->next_record(MYSQLI_ASSOC, false); + if (!$row) { + break; + } + if (isset($latest[$row['groupId']])) { + continue; + } else { + $paranoia = unserialize($row['paranoia']); + if (is_array($paranoia) && in_array('uploads', $paranoia)) { + continue; + } + } + $row['name'] = \Torrents::display_string($row['groupId'], \Torrents::DISPLAYSTRING_SHORT); + $latest[$row['groupId']] = $row; + } + $this->cache->cache_value(self::CACHE_KEY_LATEST_UPLOADS . $limit, $latest, 86400); + } + return $latest; + } + + /** + * Flush the most recent uploads (e.g. a new lossless upload is made). + * + * Note: Since arbitrary N may have been cached, all uses of N latest + * uploads must be flushed when invalidating, following a new upload. + * grep is your friend. This also assumes that there is sufficient + * activity to not have to worry about a very recent upload being + * deleted for whatever reason. For a start, even if the list becomes + * briefly out of date, the next upload will regenerate the list. + * + * @param int $limit + */ + public function flushLatestUploads(int $limit) { + $this->cache->delete_value(self::CACHE_KEY_LATEST_UPLOADS . $limit); + } + + protected function featuredAlbum(int $type): array { + $key = sprintf(self::CACHE_KEY_FEATURED, $type); + if (($featured = $this->cache->get_value($key)) === false) { + $featured = $this->db->rowAssoc(" + SELECT fa.GroupID, + tg.Name, + tg.WikiImage, + fa.ThreadID, + fa.Title + FROM featured_albums AS fa + INNER JOIN torrents_group AS tg ON (tg.ID = fa.GroupID) + WHERE Ended IS NULL AND type = ? + ", $type + ); + if (!is_null($featured)) { + $featured['artist_name'] = \Artists::display_artists(\Artists::get_artist($featured['GroupID']), false, false); + $featured['image'] = \ImageTools::process($featured['WikiImage'], true); + } + $this->cache->cache_value($key, $featured, 86400 * 7); + } + return $featured ?? []; + } + + public function featuredAlbumAotm(): array { + return $this->featuredAlbum(self::FEATURED_AOTM); + } + + public function featuredAlbumShowcase(): array { + return $this->featuredAlbum(self::FEATURED_SHOWCASE); + } +} diff --git a/app/Manager/Torrent.php b/app/Manager/Torrent.php index 2142d30d7..94482af63 100644 --- a/app/Manager/Torrent.php +++ b/app/Manager/Torrent.php @@ -9,23 +9,17 @@ class Torrent extends \Gazelle\Base { /* **** To display a torrent name, edition and flags, at the minimum the code looks like: - $torMan = new Gazelle\Manager\Torrent; $labelMan = new Gazelle\Manager\TorrentLabel; // set up the labeler once $labelMan->showMedia(true)->showEdition(true); - - // load the group and torrent ids (normally both of these are always at hand) - $torMan->setGroupId(1234)->setTorrentId(1666); - - // if only the torrentId is set, it will discover the groupId - $torMan->setTorrentId(1666); + $torrent = new Gazelle\Torrent(1666); // the artist name (A, A & B, Various Artists, Various Composers under Various Conductors etc) - echo $torMan->artistHtml(); + echo $torrent->group()->artistHtml(); // load the torrent details into the labeler - $labelMan->load($torMan->torrentInfo()[1]); + $labelMan->load($torrent->info()); // remaster info, year, etc echo $labelMan->edition(); @@ -36,17 +30,6 @@ class Torrent extends \Gazelle\Base { **** This is a bit cumbersome and subject to change */ - protected $torrentId; - protected $groupId; - protected $userId; - protected $showSnatched; - protected $snatchBucket; - protected $updateTime; - protected $tokenCache; - protected $artistDisplay; - protected $showFallbackImage = true; - protected $logger; - const FEATURED_AOTM = 0; const FEATURED_SHOWCASE = 1; @@ -63,20 +46,7 @@ class Torrent extends \Gazelle\Base { const ARTIST_DISPLAY_TEXT = 1; const ARTIST_DISPLAY_HTML = 2; - public function __construct() { - parent::__construct(); - $this->artistDisplay = self::ARTIST_DISPLAY_HTML; - } - - public function findGroupById(int $groupId) { - $id = $this->db->scalar(" - SELECT ID FROM torrents_group WHERE ID = ? - ", $groupId - ); - return $id ? (new \Gazelle\TorrentGroup($id))->setInfo($this->groupInfo($id)) : null; - } - - public function findTorrentById(int $torrentId) { + public function findById(int $torrentId) { $id = $this->db->scalar(" SELECT ID FROM torrents WHERE ID = ? ", $torrentId @@ -84,1043 +54,12 @@ public function findTorrentById(int $torrentId) { return $id ? new \Gazelle\Torrent($id) : null; } - public function findTorrentByHash(string $hash) { - $id = $this->hashToTorrentId($hash); - return $id ? new \Gazelle\Torrent($id) : null; - } - - /** - * Set context to a specific group. Used to retrieve the - * information needed to build a complete url. - * - * @param int $groupId The ID of the group - */ - public function setGroupId(int $groupId) { - $this->groupId = $groupId; - return $this; - } - - /** - * Set context to a specific torrent. Used to retrieve the - * information needed to build a complete url. - * - * @param int $torrentId The ID of the torrent (not the group!) - * @return $this to allow method chaining - */ - public function setTorrentId(int $torrentId) { - if ($this->torrentId !== $torrentId) { - $this->groupId = null; - } - $this->torrentId = $torrentId; - return $this; - } - - /** - * Toggle whether an internal URL is returnd for missing cover artwork - * is returned, or null. Used by API endpoints. - * - * @param bool false means the property will be null instead of placeholder URL - * @return $this to allow method chaining - */ - public function showFallbackImage(bool $showFallbackImage) { - $this->showFallbackImage = $showFallbackImage; - return $this; - } - - /** - * Set context to a specific user. Used to determine whether or not to display - * Personal Freeleech and Snatched indicators in torrent and group info. - * - * @param int $userId The ID of the User - * @return $this to allow method chaining - */ - public function setViewer(int $userId) { - $this->userId = $userId; - return $this; - } - - /** - * Set artist display to text - */ - public function setArtistDisplayText() { - $this->artistDisplay = self::ARTIST_DISPLAY_TEXT; - return $this; - } - - public function artistName(): string { - return $this->artistHtml(); - } - - /** - * Generate an HTML anchor or the name for an artist - */ - protected function artistLink(array $info): string { - return $this->artistDisplay === self::ARTIST_DISPLAY_HTML - ? '' . display_str($info['name']) . '' - : $info['name']; - } - - /** - * Get artist list - * - * return array artists group by role - */ - public function artistRole(): array { - $key = 'shortv2_groups_artists_' . $this->groupId; - $roleList = $this->cache->get_value($key); - if ($roleList === false) { - $this->db->prepared_query(" - SELECT ta.Importance, - ta.ArtistID, - aa.Name, - ta.AliasID - FROM torrents_artists AS ta - INNER JOIN artists_alias AS aa ON (ta.AliasID = aa.AliasID) - WHERE ta.GroupID = ? - ORDER BY ta.GroupID, ta.Importance ASC, aa.Name ASC - ", $this->groupId - ); - $map = [ - 1 => 'main', - 2 => 'guest', - 3 => 'remixer', - 4 => 'composer', - 5 => 'conductor', - 6 => 'dj', - 7 => 'producer', - 8 => 'arranger', - ]; - $roleList = [ - 'main' => [], - 'guest' => [], - 'remixer' => [], - 'composer' => [], - 'conductor' => [], - 'dj' => [], - 'producer' => [], - 'arranger' => [], - ]; - while ([$role, $artistId, $artistName, $aliasId] = $this->db->next_record(MYSQLI_NUM, false)) { - $roleList[$map[$role]][] = [ - 'id' => $artistId, - 'aliasid' => $aliasId, - 'name' => $artistName, - ]; - } - $this->cache->cache_value($key, $roleList, 3600); - } - return $roleList; - } - - /** - * Generate the artist name. (Individual artists will be clickable, or VA) - * TODO: refactor calls into artistName() - */ - public function artistHtml(): string { - static $nameCache = []; - if (isset($nameCache[$this->torrentId])) { - return $nameCache[$this->torrentId]; - } - - if (!$this->groupId) { - $this->groupId = $this->idToGroupId($this->torrentId); - } - if (!$this->torrentId && !$this->groupId) { - return $nameCache[$this->torrentId] = ''; - } - $roleList = $this->artistRole(); - $composerCount = count($roleList['composer']); - $conductorCount = count($roleList['conductor']); - $arrangerCount = count($roleList['arranger']); - $djCount = count($roleList['dj']); - $mainCount = count($roleList['main']); - if ($composerCount + $mainCount + $conductorCount + $djCount == 0) { - return $nameCache[$this->torrentId] = sprintf('(torrent id:%d)', $this->torrentId); - } - - $and = $this->artistDisplay === self::ARTIST_DISPLAY_HTML ? '&' : '&'; - $chunk = []; - if ($djCount == 1) { - $chunk[] = $this->artistLink($roleList['dj'][0]); - } elseif ($djCount == 2) { - $chunk[] = $this->artistLink($roleList['dj'][0]) . " $and " . $this->artistLink($roleList['dj'][1]); - } elseif ($djCount > 2) { - $chunk[] = 'Various DJs'; - } else { - if ($composerCount > 0) { - if ($composerCount == 1) { - $chunk[] = $this->artistLink($roleList['composer'][0]); - } elseif ($composerCount == 2) { - $chunk[] = $this->artistLink($roleList['composer'][0]) . " $and " . $this->artistLink($roleList['composer'][1]); - } elseif ($composerCount > 2 && $mainCount + $conductorCount == 0) { - $chunk[] = 'Various Composers'; - } - if ($mainCount > 0) { - $chunk[] = 'Various Composers performed by'; - } - } - - if ($composerCount > 0 - && $mainCount > 1 - && $conductorCount > 1 - ) { - $chunk[] = 'Various Artists'; - } else { - if ($mainCount == 1) { - $chunk[] = $this->artistLink($roleList['main'][0]); - } elseif ($mainCount == 2) { - $chunk[] = $this->artistLink($roleList['main'][0]) . " $and " . $this->artistLink($roleList['main'][1]); - } elseif ($mainCount > 2) { - $chunk[] = 'Various Artists'; - } - - if ($conductorCount > 0 - && $mainCount + $composerCount > 0 - && ($composerCount < 3 || $mainCount > 0) - ) { - $chunk[] = 'under'; - } - if ($conductorCount == 1) { - $chunk[] = $this->artistLink($roleList['conductor'][0]); - } elseif ($conductorCount == 2) { - $chunk[] = $this->artistLink($roleList['conductor'][0]) . " $and " . $this->artistLink($roleList['conductor'][1]); - } elseif ($conductorCount > 2) { - $chunk[] = 'Various Conductors'; - } - } - } - return $nameCache[$this->torrentId] = implode(' ', $chunk); - } - - /** - * Delete a torrent. - * setViewer() must have been called prior, to set the user removing (use 0 for system) - * setTorrentId() must have been called prior, to identify the torrent to be removed - * - * @param string $reason Why is this being deleted? (For the log) - * @param string $trackerReason The deletion reason for ocelot to report to users. - */ - public function remove(string $reason, int $trackerReason = -1): array { - $qid = $this->db->get_query_id(); - if (!$this->torrentId) { - throw new TorrentManagerIdNotSetException; - } - if ($this->userId === null) { - throw new TorrentManagerUserNotSetException; - } - - [$group, $torrent] = $this->torrentInfo(); - if ($this->torrentId > MAX_PREV_TORRENT_ID) { - (new \Gazelle\Bonus)->removePointsForUpload($torrent['UserID'], - [$torrent['Format'], $torrent['Media'], $torrent['Encoding'], $torrent['HasLogDB'], $torrent['LogScore'], $torrent['LogChecksum']]); - } - - $manager = new \Gazelle\DB; - $manager->relaxConstraints(true); - [$ok, $message] = $manager->softDelete(SQLDB, 'torrents_leech_stats', [['TorrentID', $this->torrentId]], false); - if (!$ok) { - return [false, $message]; - } - [$ok, $message] = $manager->softDelete(SQLDB, 'torrents', [['ID', $this->torrentId]]); - if (!$ok) { - return [false, $message]; - } - $manager->relaxConstraints(false); - (new \Gazelle\Tracker)->update_tracker('delete_torrent', [ - 'id' => $this->torrentId, - 'info_hash' => rawurlencode(hex2bin($torrent['InfoHash'])), - 'reason' => $trackerReason, - ]); - $this->cache->decrement('stats_torrent_count'); - - $Count = $this->db->scalar(" - SELECT count(*) FROM torrents WHERE GroupID = ? - ", $group['ID'] - ); - if ($Count > 0) { - \Torrents::update_hash($group['ID']); - } - - $manager->softDelete(SQLDB, 'torrents_files', [['TorrentID', $this->torrentId]]); - $manager->softDelete(SQLDB, 'torrents_bad_files', [['TorrentID', $this->torrentId]]); - $manager->softDelete(SQLDB, 'torrents_bad_folders', [['TorrentID', $this->torrentId]]); - $manager->softDelete(SQLDB, 'torrents_bad_tags', [['TorrentID', $this->torrentId]]); - $manager->softDelete(SQLDB, 'torrents_cassette_approved', [['TorrentID', $this->torrentId]]); - $manager->softDelete(SQLDB, 'torrents_lossymaster_approved', [['TorrentID', $this->torrentId]]); - $manager->softDelete(SQLDB, 'torrents_lossyweb_approved', [['TorrentID', $this->torrentId]]); - $manager->softDelete(SQLDB, 'torrents_missing_lineage', [['TorrentID', $this->torrentId]]); - - $this->db->prepared_query(" - INSERT INTO user_torrent_remove - (user_id, torrent_id) - VALUES (?, ?) - ", $this->userId, $this->torrentId - ); - - // Tells Sphinx that the group is removed - $this->db->prepared_query(" - REPLACE INTO sphinx_delta - (ID, Time) - VALUES (?, now()) - ", $this->torrentId - ); - - $this->db->prepared_query(" - UPDATE reportsv2 SET - Status = 'Resolved', - LastChangeTime = now(), - ModComment = 'Report already dealt with (torrent deleted)' - WHERE Status != 'Resolved' - AND TorrentID = ? - ", $this->torrentId - ); - $count = $this->db->affected_rows(); - if ($count) { - $this->cache->decrement('num_torrent_reportsv2', $count); - } - - // Torrent notifications - $this->db->prepared_query(" - SELECT concat('user_notify_upload_', UserID) as ck - FROM users_notify_torrents - WHERE TorrentID = ? - ", $this->torrentId - ); - $deleteKeys = $this->db->collect('ck', false); - $manager->softDelete(SQLDB, 'users_notify_torrents', [['TorrentID', $this->torrentId]]); - - if ($this->userId !== 0) { - $RecentUploads = $this->cache->get_value("user_recent_up_" . $this->userId); - if (is_array($RecentUploads)) { - foreach ($RecentUploads as $Key => $Recent) { - if ($Recent['ID'] == $group['ID']) { - $deleteKeys[] = "user_recent_up_" . $this->userId; - break; - } - } - } - } - - $deleteKeys[] = "torrent_download_" . $this->torrentId; - $deleteKeys[] = "torrent_group_" . $group['ID']; - $deleteKeys[] = "torrents_details_" . $group['ID']; - $this->cache->deleteMulti($deleteKeys); - - if (!$this->logger) { - $this->logger = new \Gazelle\Log; - } - $infohash = strtoupper($torrent['InfoHash']); - $sizeMB = number_format($torrent['Size'] / (1024 * 1024), 2) . ' MB'; - $username = $this->userId ? (new User)->findById($this->userId)->username() : 'system'; - $this->logger->general( - "Torrent " - . $this->torrentId - . " ({$group['Name']}) [" . (new TorrentLabel)->load($torrent)->release() - . "] ($sizeMB $infohash) was deleted by $username for reason: $reason" - ) - ->torrent( - $group['ID'], - $this->torrentId, - $this->userId, - "deleted torrent ($sizeMB $infohash) for reason: $reason" - ); - - $this->db->set_query_id($qid); - return [true, "torrent " . $this->torrentId . " removed"]; - } - - /** - * In the context of a user, determine whether snatched indicators should be - * added to torrent and group info. - * - * @param int $userID The ID of the User - * @return $this to allow method chaining - */ - public function setShowSnatched(int $showSnatched) { - $this->showSnatched = $showSnatched; - return $this; - } - - /** - * Has the viewing user snatched this torrent? (And do they want - * to know about it?) - * - * @param int $torrentId - * @return bool - */ - public function isSnatched(int $torrentId): bool { - if (!$this->userId || !$this->showSnatched) { - return false; - } - - $buckets = 64; - $bucketMask = $buckets - 1; - $bucketId = $torrentId & $bucketMask; - - $snatchKey = "users_snatched_" . $this->userId . "_time"; - if (!$this->snatchBucket) { - $this->snatchBucket = array_fill(0, $buckets, false); - $this->updateTime = $this->cache->get_value($snatchKey); - if ($this->updateTime === false) { - $this->updateTime = [ - 'last' => 0, - 'next' => 0 - ]; - } - } elseif (isset($this->snatchBucket[$bucketId][$torrentId])) { - return true; - } - - // Torrent was not found in the previously inspected snatch lists - $bucket =& $this->snatchBucket[$bucketId]; - if ($bucket === false) { - $now = time(); - // This bucket hasn't been checked before - $bucket = $this->cache->get_value($snatchKey, true); - if ($bucket === false || $now > $this->updateTime['next']) { - $bucketKeyStem = 'users_snatched_' . $this->userId . '_'; - $updated = []; - $qid = $this->db->get_query_id(); - if ($bucket === false || $this->updateTime['last'] == 0) { - for ($i = 0; $i < $buckets; $i++) { - $this->snatchBucket[$i] = []; - } - // Not found in cache. Since we don't have a suitable index, it's faster to update everything - $this->db->prepared_query(" - SELECT fid - FROM xbt_snatched - WHERE uid = ? - ", $this->userId - ); - while ([$id] = $this->db->next_record(MYSQLI_NUM, false)) { - $this->snatchBucket[$id & $bucketMask][(int)$id] = true; - } - $updated = array_fill(0, $buckets, true); - } elseif (isset($bucket[$torrentId])) { - // Old cache, but torrent is snatched, so no need to update - return true; - } else { - // Old cache, check if torrent has been snatched recently - $this->db->prepared_query(" - SELECT fid - FROM xbt_snatched - WHERE uid = ? AND tstamp >= ? - ", $this->userId, $this->updateTime['last'] - ); - while ([$id] = $this->db->next_record(MYSQLI_NUM, false)) { - $bucketId = $id & $bucketMask; - if ($this->snatchBucket[$bucketId] === false) { - $this->snatchBucket[$bucketId] = $this->cache->get_value("$bucketKeyStem$bucketId", true); - if ($this->snatchBucket[$bucketId] === false) { - $this->snatchBucket[$bucketId] = []; - } - } - $this->snatchBucket[$bucketId][(int)$id] = true; - $updated[$bucketId] = true; - } - } - $this->db->set_query_id($qid); - for ($i = 0; $i < $buckets; $i++) { - if (isset($updated[$i])) { - $this->cache->cache_value("$bucketKeyStem$i", $this->snatchBucket[$i], 7200); - } - } - $this->updateTime['last'] = $now; - $this->updateTime['next'] = $now + self::SNATCHED_UPDATE_INTERVAL; - $this->cache->cache_value($snatchKey, $this->updateTime, 7200); - } - } - return isset($bucket[$torrentId]); - } - - /** - * Check if the viwer has an active freeleech token - * setViewer() must be called beforehand - * - * @param int $torrentId - * @return true if an active token exists for the viewer - */ - public function hasToken(int $torrentId): bool { - if (!$this->userId) { - return false; - } - if (!$this->tokenCache) { - $key = "users_tokens_" . $this->userId; - $this->tokenCache = $this->cache->get_value($key); - if ($this->tokenCache === false) { - $qid = $this->db->get_query_id(); - $this->db->prepared_query(" - SELECT TorrentID - FROM users_freeleeches - WHERE Expired = 0 AND UserID = ? - ", - $this->userId - ); - $this->tokenCache = array_fill_keys($this->db->collect('TorrentID', false), true); - $this->db->set_query_id($qid); - $this->cache->cache_value($key, $this->tokenCache, 3600); - } - } - return isset($this->tokenCache[$torrentId]); - } - - public function expireToken(int $userId, int $torrentId): bool { - $hash = $this->db->scalar(" - SELECT info_hash FROM torrents WHERE ID = ? - ", $torrentId - ); - if (!$hash) { - return false; - } - $this->db->prepared_query(" - UPDATE users_freeleeches SET - Expired = true - WHERE UserID = ? - AND TorrentID = ? - ", $userId, $torrentId - ); - $this->cache->delete_value("users_tokens_{$userId}"); - (new \Gazelle\Tracker)->update_tracker('remove_token', ['info_hash' => rawurlencode($hash), 'userid' => $userId]); - return true; - } - - public function groupInfo(int $groupId, int $revisionId = 0): ?array { - if (!$groupId) { - return null; - } - $cached = null; - if (!$revisionId) { - $cached = $this->cache->get_value("torrents_details_$groupId"); - } - if (!$revisionId && is_array($cached)) { - [$group, $torrentList] = $cached; - } else { - // Fetch the group details - - $SQL = 'SELECT ' - . ($revisionId ? 'w.Body, w.Image,' : 'g.WikiBody, g.WikiImage,') - . " g.ID, - g.Name, - g.Year, - g.RecordLabel, - g.CatalogueNumber, - g.ReleaseType, - g.CategoryID, - g.Time, - g.VanityHouse, - group_concat(DISTINCT tags.Name SEPARATOR '|') AS tagNames, - group_concat(DISTINCT tags.ID SEPARATOR '|') AS tagIds, - group_concat(tt.UserID SEPARATOR '|') AS tagVoteUserIds, - group_concat(tt.PositiveVotes SEPARATOR '|') AS tagUpvotes, - group_concat(tt.NegativeVotes SEPARATOR '|') AS tagDownvotes - FROM torrents_group AS g - LEFT JOIN torrents_tags AS tt ON (tt.GroupID = g.ID) - LEFT JOIN tags ON (tags.ID = tt.TagID) - "; - - $args = []; - if ($revisionId) { - $SQL .= ' - LEFT JOIN wiki_torrents AS w ON (w.PageID = ? AND w.RevisionID = ?)'; - $args[] = $groupId; - $args[] = $revisionId; - } - - $SQL .= " - WHERE g.ID = ? - GROUP BY g.ID"; - $args[] = $groupId; - - $this->db->prepared_query($SQL, ...$args); - $group = $this->db->next_record(MYSQLI_ASSOC, false); - - // Fetch the individual torrents - $columns = " - t.ID, - t.Media, - t.Format, - t.Encoding, - t.Remastered, - t.RemasterYear, - t.RemasterTitle, - t.RemasterRecordLabel, - t.RemasterCatalogueNumber, - t.Scene, - t.HasLog, - t.HasCue, - t.HasLogDB, - t.LogScore, - t.LogChecksum, - t.FileCount, - t.Size, - tls.Seeders, - tls.Leechers, - tls.Snatched, - t.FreeTorrent, - t.Time, - t.Description, - t.FileList, - t.FilePath, - t.UserID, - tls.last_action, - HEX(t.info_hash) AS InfoHash, - tbt.TorrentID AS BadTags, - tbf.TorrentID AS BadFolders, - tfi.TorrentID AS BadFiles, - ml.TorrentID AS MissingLineage, - ca.TorrentID AS CassetteApproved, - lma.TorrentID AS LossymasterApproved, - lwa.TorrentID AS LossywebApproved, - t.LastReseedRequest, - t.ID AS HasFile, - group_concat(tl.LogID) as ripLogIds - "; - - $this->db->prepared_query(" - SELECT $columns, 0 as is_deleted - FROM torrents AS t - INNER JOIN torrents_leech_stats tls ON (tls.TorrentID = t.ID) - LEFT JOIN torrents_bad_tags AS tbt ON (tbt.TorrentID = t.ID) - LEFT JOIN torrents_bad_folders AS tbf ON (tbf.TorrentID = t.ID) - LEFT JOIN torrents_bad_files AS tfi ON (tfi.TorrentID = t.ID) - LEFT JOIN torrents_missing_lineage AS ml ON (ml.TorrentID = t.ID) - LEFT JOIN torrents_cassette_approved AS ca ON (ca.TorrentID = t.ID) - LEFT JOIN torrents_lossymaster_approved AS lma ON (lma.TorrentID = t.ID) - LEFT JOIN torrents_lossyweb_approved AS lwa ON (lwa.TorrentID = t.ID) - LEFT JOIN torrents_logs AS tl ON (tl.TorrentID = t.ID) - WHERE t.GroupID = ? - GROUP BY t.ID - UNION DISTINCT - SELECT $columns, 1 as is_deleted - FROM deleted_torrents AS t - INNER JOIN deleted_torrents_leech_stats tls ON (tls.TorrentID = t.ID) - LEFT JOIN deleted_torrents_bad_tags AS tbt ON (tbt.TorrentID = t.ID) - LEFT JOIN deleted_torrents_bad_folders AS tbf ON (tbf.TorrentID = t.ID) - LEFT JOIN deleted_torrents_bad_files AS tfi ON (tfi.TorrentID = t.ID) - LEFT JOIN deleted_torrents_missing_lineage AS ml ON (ml.TorrentID = t.ID) - LEFT JOIN deleted_torrents_cassette_approved AS ca ON (ca.TorrentID = t.ID) - LEFT JOIN deleted_torrents_lossymaster_approved AS lma ON (lma.TorrentID = t.ID) - LEFT JOIN deleted_torrents_lossyweb_approved AS lwa ON (lwa.TorrentID = t.ID) - LEFT JOIN torrents_logs AS tl ON (tl.TorrentID = t.ID) - WHERE t.GroupID = ? - GROUP BY t.ID - ORDER BY Remastered ASC, - (RemasterYear != 0) DESC, - RemasterYear ASC, - RemasterTitle ASC, - RemasterRecordLabel ASC, - RemasterCatalogueNumber ASC, - Media ASC, - Format, - Encoding, - ID - ", $groupId, $groupId - ); - $torrentList = $this->db->to_array('ID', MYSQLI_ASSOC, false); - if (empty($group) || empty($torrentList)) { - return null; - } - - if (!$revisionId) { - $this->cache->cache_value("torrents_details_$groupId", [$group, $torrentList], - in_array(0, $this->db->collect('Seeders')) ? 600 : 3600 - ); - } - } - - // Fetch all user specific torrent and group properties - $group['Flags'] = ['IsSnatched' => false]; - foreach ($torrentList as &$t) { - $t['PersonalFL'] = empty($t['FreeTorrent']) && $this->hasToken($t['ID']); - if ($t['IsSnatched'] = $this->isSnatched($t['ID'])) { - $group['IsSnatched'] = true; - } - } - unset($t); - - // make the values sane (null, boolean as appropriate) - // TODO: once all get_*_info calls have been ported over, do this prior to caching - foreach (['CatalogueNumber', 'RecordLabel'] as $nullable) { - $group[$nullable] = $group[$nullable] == '' ? null : $group[$nullable]; - } - if (!$group['WikiImage']) { - if (!$this->showFallbackImage) { - $group['WikiImage'] = null; - } else { - global $CategoryIcons; - $group['WikiImage'] = STATIC_SERVER.'/common/noartwork/' - . $CategoryIcons[$group['CategoryID'] - 1]; - } - } - $group['VanityHouse'] = ($group['VanityHouse'] == 1); - $group['ReleaseType'] = (int)$group['ReleaseType']; - - // Reorganize tag info to be useful - $tagIds = explode('|', $group['tagIds']); - $tagNames = explode('|', $group['tagNames']); - $tagVoteUserIds = explode('|', $group['tagVoteUserIds']); - $tagUpvotes = explode('|', $group['tagUpvotes']); - $tagDownvotes = explode('|', $group['tagDownvotes']); - $group['tags'] = []; - for ($n = 0; $n < count($tagIds); ++$n) { - $group['tags'][$tagIds[$n]] = [ - 'name' => $tagNames[$n], - 'userId' => $tagVoteUserIds[$n], - 'upvotes' => $tagUpvotes[$n], - 'downvotes' => $tagDownvotes[$n], - ]; - } - - foreach ($torrentList as &$torrent) { - foreach (['last_action', 'LastReseedRequest', 'RemasterCatalogueNumber', 'RemasterRecordLabel', 'RemasterTitle', 'RemasterYear'] - as $nullable - ) { - $torrent[$nullable] = $torrent[$nullable] == '' ? null : $torrent[$nullable]; - } - foreach (['FreeTorrent', 'HasCue', 'HasLog', 'HasLogDB', 'LogChecksum', 'Remastered', 'Scene'] - as $zerotruth - ) { - $torrent[$zerotruth] = !($torrent[$zerotruth] == '0'); - } - foreach (['BadFiles', 'BadFolders', 'BadTags', 'CassetteApproved', 'LossymasterApproved', 'LossywebApproved', 'MissingLineage'] - as $emptytruth - ) { - $torrent[$emptytruth] = !($torrent[$emptytruth] == ''); - } - $torrent['ripLogIds'] = empty($torrent['ripLogIds']) - ? [] : array_map(function ($x) { return (int)$x; }, explode(',', $torrent['ripLogIds'])); - $torrent['LogCount'] = count($torrent['ripLogIds']); - } - return [$group, $torrentList]; - } - - public function torrentInfo($revisionId = 0) { - if (!$this->groupId) { - $this->groupId = $this->idToGroupId($this->torrentId); - if (!$this->groupId) { - return null; - } - } - - if (!($info = $this->groupInfo($this->groupId, $revisionId))) { - return null; - } - return [$info[0], $info[1][$this->torrentId]]; - } - - /** - * Is this a valid torrenthash? - * @param string $hash - * @return string|bool The hash (with any spaces removed) if valid, otherwise false - */ - public function isValidHash(string $hash) { - //6C19FF4C 6C1DD265 3B25832C 0F6228B2 52D743D5 - $hash = str_replace(' ', '', $hash); - return preg_match('/^[0-9a-fA-F]{40}$/', $hash) ? $hash : false; - } - - /** - * Map a torrenthash to a torrent id - * @param string $hash - * @return int The torrent id if found, otherwise null - */ - public function hashToTorrentId(string $hash): ?int { - return $this->db->scalar(" + public function findByInfohash(string $hash) { + $id = $this->db->scalar(" SELECT ID FROM torrents WHERE info_hash = UNHEX(?) ", $hash ); - } - - /** - * Map a torrenthash to a group id - * @param string $hash - * @return int The group id if found, otherwise null - */ - public function hashToGroupId(string $hash) { - return $this->db->scalar(" - SELECT GroupID FROM torrents WHERE info_hash = UNHEX(?) - ", $hash - ); - } - - /** - * Map a torrenthash to a torrent id and its group id - * @param string $hash - * @return array The torrent id and group id if found, otherwise null - */ - public function hashToTorrentGroup(string $hash) { - $key = "thash_to_group_$hash"; - if (($info = $this->cache->get_value($key)) === false) { - $info = $this->db->row(" - SELECT ID, GroupID FROM torrents WHERE info_hash = UNHEX(?) - ", $hash - ); - $this->cache->cache_value($key, $info, 86400 + rand(0, 3600)); - } - return $info; - } - - /** - * Map a torrent id to a group id - * @param int $torrentId - * @return int The group id if found, otherwise null - */ - public function idToGroupId(int $torrentId) { - $key = "tid_to_group_$torrentId"; - if (($groupId = $this->cache->get_value($key)) === false) { - $groupId = $this->db->scalar(" - SELECT GroupID FROM torrents WHERE ID = ? - ", $torrentId - ); - $this->cache->cache_value($key, $groupId, 86400 + rand(0, 3600)); - } - return $groupId; - } - - /** - * How many unresolved torrent reports are there in this group? - * @param int Group ID - * @return int number of unresolved reports - */ - public function unresolvedGroupReports(int $groupId): int { - return $this->db->scalar(" - SELECT count(*) - FROM reportsv2 AS r - INNER JOIN torrents AS t ON (t.ID = r.TorrentID) - WHERE r.Status != 'Resolved' - AND t.GroupID = ? - ", $groupId - ); - } - - /** - * How many unresolved torrent reports are there for this user? - * @param int User ID - * @return int number of unresolved reports - */ - public function unresolvedUserReports(int $userId): int { - return $this->db->scalar(" - SELECT count(*) - FROM reportsv2 AS r - INNER JOIN torrents AS t ON (t.ID = r.TorrentID) - WHERE r.Status != 'Resolved' - AND t.UserID = ? - ", $userId - ); - } - - /** - * Get the requests filled by this torrent. - * (Should only be one, but hey, who knows what the original developer was looking to catch?) - * @param int torrent ID - * @return DB object to loop over [request id, filler user id, date filled] - */ - public function requestFills(int $torrentId): array { - $this->db->prepared_query(" - SELECT r.ID, r.FillerID, r.TimeFilled FROM requests AS r WHERE r.TorrentID = ? - ", $torrentId - ); - return $this->db->to_array(false, MYSQLI_NUM, false); - } - - /** - * Return the N most recent lossless uploads - * Note that if both a Lossless and 24bit Lossless are uploaded at the same time, - * only one entry will be returned, to ensure that the result is comprised of N - * different groups. Uploads of paranoid users are excluded. Uploads without - * cover art are excluded. - * - * @param int $limit - * @return array of [imageUrl, groupId, torrentId, uploadDate, username, paranoia] - */ - public function latestUploads(int $limit) { - if (!($latest = $this->cache->get_value(self::CACHE_KEY_LATEST_UPLOADS . $limit))) { - $this->db->prepared_query(" - SELECT tg.WikiImage AS imageUrl, - R.GroupID AS groupId, - R.torrentId, - R.uploadDate, - um.Username AS username, - um.Paranoia AS paranoia, - group_concat(tag.Name ORDER BY tag.Name SEPARATOR ', ') AS tags - FROM ( - SELECT t.GroupID, - max(t.ID) AS torrentId, - max(t.Time) AS uploadDate - FROM torrents t - INNER JOIN torrents_group tg ON (tg.ID = t.GroupID) - WHERE t.Time > now() - INTERVAL 3 DAY - AND t.Encoding IN ('Lossless', '24bit Lossless') - AND tg.WikiImage != '' - AND NOT EXISTS ( - SELECT 1 - FROM torrents_tags ttex - WHERE t.GroupID = ttex.GroupID - AND ttex.TagID IN (" . placeholders(HOMEPAGE_TAG_IGNORE) . ") - ) - GROUP BY t.GroupID - ) R - INNER JOIN torrents_group tg ON (tg.ID = R.groupId) - INNER JOIN torrents_tags tt USING (GroupID) - INNER JOIN tags tag ON (tag.ID = tt.TagID) - INNER JOIN torrents t ON (t.ID = R.torrentId) - INNER JOIN users_main um ON (um.ID = t.UserID) - GROUP BY R.GroupID - ORDER BY R.uploadDate DESC - ", ...HOMEPAGE_TAG_IGNORE - ); - $latest = []; - while (count($latest) < $limit) { - $row = $this->db->next_record(MYSQLI_ASSOC, false); - if (!$row) { - break; - } - if (isset($latest[$row['groupId']])) { - continue; - } else { - $paranoia = unserialize($row['paranoia']); - if (is_array($paranoia) && in_array('uploads', $paranoia)) { - continue; - } - } - $row['name'] = \Torrents::display_string($row['groupId'], \Torrents::DISPLAYSTRING_SHORT); - $latest[$row['groupId']] = $row; - } - $this->cache->cache_value(self::CACHE_KEY_LATEST_UPLOADS . $limit, $latest, 86400); - } - return $latest; - } - - /** - * Flush the most recent uploads (e.g. a new lossless upload is made). - * - * Note: Since arbitrary N may have been cached, all uses of N latest - * uploads must be flushed when invalidating, following a new upload. - * grep is your friend. This also assumes that there is sufficient - * activity to not have to worry about a very recent upload being - * deleted for whatever reason. For a start, even if the list becomes - * briefly out of date, the next upload will regenerate the list. - * - * @param int $limit - */ - public function flushLatestUploads(int $limit) { - $this->cache->delete_value(self::CACHE_KEY_LATEST_UPLOADS . $limit); - } - - /** - * Regenerate a torrent's file list from its meta data, - * update the database record and clear relevant cache keys - * - * @param int torrentId - * @return int number of files regenned - */ - public function regenerateFilelist(int $torrentId): int { - $qid = $this->db->get_query_id(); - $groupId = $this->db->scalar(" - SELECT t.GroupID FROM torrents AS t WHERE t.ID = ? - ", $torrentId - ); - $n = 0; - if ($groupId) { - $Tor = new \OrpheusNET\BencodeTorrent\BencodeTorrent; - $Tor->decodeString($str = (new \Gazelle\File\Torrent())->get($torrentId)); - $TorData = $Tor->getData(); - ['total_size' => $TotalSize, 'files' => $FileList] = $Tor->getFileList(); - $TmpFileList = []; - foreach ($FileList as $file) { - $TmpFileList[] = $this->metaFilename($file['path'], $file['size']); - ++$n; - } - $this->db->prepared_query(" - UPDATE torrents SET - Size = ?, - FilePath = ?, - FileList = ? - WHERE ID = ? - ", $TotalSize, - (isset($TorData['info']['files']) ? make_utf8($Tor->getName()) : ''), - implode("\n", $TmpFileList), - $torrentId - ); - $this->cache->delete_value("torrents_details_$groupId"); - } - $this->db->set_query_id($qid); - return $n; - } - - /** - * Set the source flag of the torrent as appropriate - * - * @param OrpheusNET\BencodeTorrent\BencodeTorrent torrent to be checked - * @return bool flag was set (implies modified infohash) - */ - public function setSourceFlag(\OrpheusNET\BencodeTorrent\BencodeTorrent $torrent): bool { - $sourceFlag = $torrent->getSource(); - if ($sourceFlag === SOURCE) { - return false; - } - - $creationDate = $torrent->getCreationDate(); - if (!is_null($creationDate)) { - if (is_null($sourceFlag) && $creationDate <= GRANDFATHER_NO_SOURCE) { - return false; - } - elseif ($sourceFlag === GRANDFATHER_SOURCE && $creationDate <= GRANDFATHER_OLD_SOURCE) { - return false; - } - } - return $torrent->setSource(SOURCE); - } - - public function peerlistTotal(int $torrentId) { - $key = sprintf(self::CACHE_KEY_PEERLIST_TOTAL, $torrentId); - if (($total = $this->cache->get_value($key)) === false) { - // force flush the first page of results - $this->cache->delete_value(sprintf(self::CACHE_KEY_PEERLIST_PAGE, $torrentId, 0)); - $total = $this->db->scalar(" - SELECT count(*) - FROM xbt_files_users AS xfu - INNER JOIN users_main AS um ON (um.ID = xfu.uid) - INNER JOIN torrents AS t ON (t.ID = xfu.fid) - WHERE um.Visible = '1' - AND xfu.fid = ? - ", $torrentId - ); - $this->cache->cache_value($key, $total, 300); - } - return $total; - } - - public function peerlistPage(int $torrentId, int $userId, int $limit, int $offset) { - $key = sprintf(self::CACHE_KEY_PEERLIST_PAGE, $torrentId, $offset); - if (($list = $this->cache->get_value($key)) === false) { - // force flush the next page of results - $this->cache->delete_value(sprintf(self::CACHE_KEY_PEERLIST_PAGE, $torrentId, $offset + $limit)); - $this->db->prepared_query(" - SELECT - xfu.active, - xfu.connectable, - xfu.remaining, - xfu.uploaded, - xfu.useragent, - xfu.ip AS ipv4addr, - xfu.uid AS user_id, - t.Size AS size, - sx.name AS seedbox - FROM xbt_files_users AS xfu - INNER JOIN users_main AS um ON (um.ID = xfu.uid) - INNER JOIN torrents AS t ON (t.ID = xfu.fid) - LEFT JOIN user_seedbox sx ON (xfu.ip = inet_ntoa(sx.ipaddr) AND xfu.useragent = sx.useragent AND xfu.uid = ?) - WHERE um.Visible = '1' - AND xfu.fid = ? - ORDER BY xfu.uid = ? DESC, xfu.uploaded DESC - LIMIT ? OFFSET ? - ", $userId, $torrentId, $userId, $limit, $offset - ); - $list = $this->db->to_array(false, MYSQLI_ASSOC); - $this->cache->cache_value($key, $list, 300); - } - return $list; + return $id ? new \Gazelle\Torrent($id) : null; } public function missingLogfiles(int $userId): array { @@ -1213,140 +152,6 @@ public function featuredAlbumShowcase(): array { return $this->featuredAlbum(self::FEATURED_SHOWCASE); } - public function modifyLogscore(int $groupId, int $torrentId): int { - $count = $this->db->scalar(" - SELECT count(*) FROM torrents_logs WHERE TorrentID = ? - ", $torrentId - ); - if (!$count) { - $this->db->prepared_query(" - UPDATE torrents SET - HasLogDB = '0', - LogChecksum = '1', - LogScore = 0 - WHERE ID = ? - ", $torrentId - ); - } else { - $this->db->prepared_query(" - UPDATE torrents AS t - LEFT JOIN ( - SELECT TorrentID, - min(CASE WHEN Adjusted = '1' THEN AdjustedScore ELSE Score END) AS Score, - min(CASE WHEN Adjusted = '1' THEN AdjustedChecksum ELSE Checksum END) AS Checksum - FROM torrents_logs - WHERE TorrentID = ? - GROUP BY TorrentID - ) AS tl ON (t.ID = tl.TorrentID) - SET - t.LogScore = tl.Score, - t.LogChecksum = tl.Checksum - WHERE t.ID = ? - ", $torrentId, $torrentId - ); - } - $this->cache->deleteMulti(["torrent_group_{$groupId}", "torrents_details_{$groupId}"]); - return $this->db->affected_rows(); - } - - public function adjustLogscore(int $groupId, int $torrentId, int $logId, $Adjusted, int $adjScore, $adjChecksum, $adjBy, $adjReason, $adjDetails): int { - $this->db->prepared_query(" - UPDATE torrents_logs SET - Adjusted = ?, AdjustedScore = ?, AdjustedChecksum = ?, AdjustedBy = ?, AdjustmentReason = ?, AdjustmentDetails = ? - WHERE LogID = ? AND TorrentID = ? - ", $Adjusted, $adjScore, $adjChecksum, $adjBy, $adjReason, $adjDetails, - $logId, $torrentId - ); - if ($this->db->affected_rows() > 0) { - return $this->modifyLogscore($groupId, $torrentId); - } - return 0; - } - - public function rescoreLog(int $groupId, int $torrentId, int $logId, \Gazelle\Logfile $logfile, string $version): int { - $this->db->prepared_query(" - UPDATE torrents_logs SET - Score = ?, `Checksum` = ?, ChecksumState = ?, Ripper = ?, RipperVersion = ?, - `Language` = ?, Details = ?, LogcheckerVersion = ?, - Adjusted = '0' - WHERE LogID = ? AND TorrentID = ? - ", $logfile->score(), $logfile->checksumStatus(), $logfile->checksumState(), $logfile->ripper(), $logfile->ripperVersion(), - $logfile->language(), $logfile->detailsAsString(), $version, - $logId, $torrentId - ); - if ($this->db->affected_rows() > 0) { - return $this->modifyLogscore($groupId, $torrentId); - } - return 0; - } - - /** - * Combine torrent media into a standardized file name - * - * @param array Torrent metadata - * @param bool whether to use .txt or .torrent as file extension - * @param int $MaxLength maximum file name length - * @return string file name with at most $MaxLength characters - */ - public function torrentFilename(array $info, bool $asText, $MaxLength = MAX_PATH_LEN) { - $MaxLength -= ($asText ? 4 : 8); - if ($info['TorrentID'] !== false) { - $MaxLength -= (strlen($info['TorrentID']) + 1); - } - $artist = safeFilename($info['Artist']); - if ($info['Year'] > 0) { - $artist .= ".{$info['Year']}"; - } - $meta = []; - if ($info['Media'] != '') { - $meta[] = $info['Media']; - } - if ($info['Format'] != '') { - $meta[] = $info['Format']; - } - if ($info['Encoding'] != '') { - $meta[] = $info['Encoding']; - } - $label = empty($meta) ? '' : ('.(' . safeFilename(implode('-', $meta)) . ')'); - - $filename = safeFilename($info['Name']); - if (!$filename) { - $filename = 'Unnamed'; - } elseif (mb_strlen("$artist.$filename$label", 'UTF-8') <= $MaxLength) { - $filename = "$artist.$filename"; - } - - $filename = shortenString($filename . $label, $MaxLength, true, false); - if ($info['TorrentID'] !== false) { - $filename .= "-{$info['TorrentID']}"; - } - return $asText ? "$filename.txt" : "$filename.torrent"; - } - - /** - * Convert a stored torrent into a binary file that can be loaded in a torrent client - * - * @param mixed $TorrentData bencoded torrent without announce URL - * @param string $AnnounceURL - * @param int $TorrentID - * @return string bencoded string - */ - public function torrentBody(int $TorrentID, string $AnnounceURL): string { - $filer = new \Gazelle\File\Torrent; - $contents = $filer->get($TorrentID); - if (is_null($contents)) { - return ''; - } - $Tor = new \OrpheusNET\BencodeTorrent\BencodeTorrent(); - $Tor->decodeString($contents); - $Tor->cleanDataDictionary(); - $Tor->setValue([ - 'announce' => $AnnounceURL, - 'comment' => SITE_URL . "/torrents.php?torrentid=$TorrentID", - ]); - return $Tor->getEncode(); - } - /** * Create a string that contains file info in a format that's easy to use for Sphinx * @@ -1388,7 +193,6 @@ public function apiFilename(string $metaname): string { return $info['name'] . '{{{' . $info['size'] . '}}}'; } -// Count the number of audio files in a torrent file list per audio type /** * Aggregate the audio files per audio type * diff --git a/app/Manager/User.php b/app/Manager/User.php index 584849465..84370b392 100644 --- a/app/Manager/User.php +++ b/app/Manager/User.php @@ -512,6 +512,22 @@ public function totalBannedForRatio(): int { "); } + /** + * How many unresolved torrent reports are there for this user? + * @param int User ID + * @return int number of unresolved reports + */ + public function unresolvedReportsTotal(int $userId): int { + return $this->db->scalar(" + SELECT count(*) + FROM reportsv2 AS r + INNER JOIN torrents AS t ON (t.ID = r.TorrentID) + WHERE r.Status != 'Resolved' + AND t.UserID = ? + ", $userId + ); + } + /** * Sends a PM from $FromId to $ToId. * diff --git a/app/Seedbox.php b/app/Seedbox.php index 27370063c..31bf2f9cd 100644 --- a/app/Seedbox.php +++ b/app/Seedbox.php @@ -159,10 +159,7 @@ public function torrentList(int $limit, int $offset, Manager\Torrent $torMan, Ma 'id' => $tid, 'folder' => $details['FilePath'], 'sortname' => $details['Name'], - 'artist' => $torMan - ->setGroupId($details['GroupID']) - ->setTorrentId($tid) - ->artistHtml(), + 'artist' => $torMan->findById($tid)->group()->artistHtml(), 'name' => sprintf('%s (%s) [%s]', $details['GroupID'], $tid, diff --git a/app/TGroup.php b/app/TGroup.php new file mode 100644 index 000000000..92eb522cf --- /dev/null +++ b/app/TGroup.php @@ -0,0 +1,461 @@ +showFallbackImage = $showFallbackImage; + return $this; + } + + /** + * Get the metadata of the torrent + * + * @return array of many things + */ + public function info(int $revisionId = 0): ?array { + $key = sprintf(self::CACHE_KEY, $this->id); + $this->revisionId = $revisionId; + if (!$revisionId) { + $cached = $this->cache->get_value($key); + if (is_array($cached)) { + // return $cached; + } + } + $sql = 'SELECT ' + . ($this->revisionId ? 'w.Body, w.Image,' : 'g.WikiBody, g.WikiImage,') + . " g.ID, + g.Name, + g.Year, + g.RecordLabel, + g.CatalogueNumber, + g.ReleaseType, + g.CategoryID, + g.Time, + g.VanityHouse, + group_concat(DISTINCT tags.Name SEPARATOR '|') AS tagNames, + group_concat(DISTINCT tags.ID SEPARATOR '|') AS tagIds, + group_concat(tt.UserID SEPARATOR '|') AS tagVoteUserIds, + group_concat(tt.PositiveVotes SEPARATOR '|') AS tagUpvotes, + group_concat(tt.NegativeVotes SEPARATOR '|') AS tagDownvotes + FROM torrents_group AS g + LEFT JOIN torrents_tags AS tt ON (tt.GroupID = g.ID) + LEFT JOIN tags ON (tags.ID = tt.TagID) + "; + + $args = []; + if ($this->revisionId) { + $sql .= ' + LEFT JOIN wiki_torrents AS w ON (w.PageID = ? AND w.RevisionID = ?)'; + $args[] = $this->id; + $args[] = $this->revisionId; + } + $sql .= " WHERE g.ID = ? GROUP BY g.ID"; + $args[] = $this->id; + + $info = $this->db->rowAssoc($sql, ...$args); + + // make the values sane (null, boolean as appropriate) + // TODO: once all get_*_info calls have been ported over, do this prior to caching + foreach (['CatalogueNumber', 'RecordLabel'] as $nullable) { + $info[$nullable] = $info[$nullable] == '' ? null : $info[$nullable]; + } + if (!$info['WikiImage']) { + if (!$this->showFallbackImage) { + $info['WikiImage'] = null; + } else { + global $CategoryIcons; + $info['WikiImage'] = STATIC_SERVER . '/common/noartwork/' . $CategoryIcons[$info['CategoryID'] - 1]; + } + } + $info['VanityHouse'] = ($info['VanityHouse'] == 1); + $info['ReleaseType'] = (int)$info['ReleaseType']; + + // Reorganize tag info to be useful + $tagIds = explode('|', $info['tagIds']); + $tagNames = explode('|', $info['tagNames']); + $tagVoteUserIds = explode('|', $info['tagVoteUserIds']); + $tagUpvotes = explode('|', $info['tagUpvotes']); + $tagDownvotes = explode('|', $info['tagDownvotes']); + $info['tags'] = []; + for ($n = 0; $n < count($tagIds); ++$n) { + $info['tags'][$tagIds[$n]] = [ + 'name' => $tagNames[$n], + 'userId' => $tagVoteUserIds[$n], + 'upvotes' => $tagUpvotes[$n], + 'downvotes' => $tagDownvotes[$n], + ]; + } + + if (!$this->revisionId) { + $this->cache->cache_value($key, $info, 0); + } + return $info; + } + + /** + * Get artist list + * + * return array artists group by role + */ + public function artistRole(): array { + $key = 'shortv2_groups_artists_' . $this->id; + $roleList = $this->cache->get_value($key); + if ($roleList === false) { + $this->db->prepared_query(" + SELECT ta.Importance, + ta.ArtistID, + aa.Name, + ta.AliasID + FROM torrents_artists AS ta + INNER JOIN artists_alias AS aa ON (ta.AliasID = aa.AliasID) + WHERE ta.GroupID = ? + ORDER BY ta.GroupID, ta.Importance ASC, aa.Name ASC + ", $this->id + ); + $map = [ + 1 => 'main', + 2 => 'guest', + 3 => 'remixer', + 4 => 'composer', + 5 => 'conductor', + 6 => 'dj', + 7 => 'producer', + 8 => 'arranger', + ]; + $roleList = [ + 'main' => [], + 'guest' => [], + 'remixer' => [], + 'composer' => [], + 'conductor' => [], + 'dj' => [], + 'producer' => [], + 'arranger' => [], + ]; + while ([$role, $artistId, $artistName, $aliasId] = $this->db->next_record(MYSQLI_NUM, false)) { + $roleList[$map[$role]][] = [ + 'id' => $artistId, + 'aliasid' => $aliasId, + 'name' => $artistName, + ]; + } + $this->cache->cache_value($key, $roleList, 3600); + } + return $roleList; + } + + public function name(): string { + return $this->info()['Name']; + } + + public function artistName(): string { + return $this->artistHtml(self::ARTIST_DISPLAY_TEXT); + } + + /** + * Generate an HTML anchor or the name for an artist + */ + protected function artistLink(array $info, int $renderMode): string { + return $renderMode === self::ARTIST_DISPLAY_HTML + ? '' . display_str($info['name']) . '' + : $info['name']; + } + + /** + * Generate the artist name. (Individual artists will be clickable, or VA) + * TODO: refactor calls into artistName() + */ + public function artistHtml(int $renderMode = self::ARTIST_DISPLAY_HTML): string { + static $nameCache = [self::ARTIST_DISPLAY_HTML => [], self::ARTIST_DISPLAY_TEXT => []]; + if (isset($nameCache[$renderMode][$this->id])) { + return $nameCache[$renderMode][$this->id]; + } + + $roleList = $this->artistRole(); + $composerCount = count($roleList['composer']); + $conductorCount = count($roleList['conductor']); + $arrangerCount = count($roleList['arranger']); + $djCount = count($roleList['dj']); + $mainCount = count($roleList['main']); + if ($composerCount + $mainCount + $conductorCount + $djCount == 0) { + return $nameCache[$renderMode][$this->id] = sprintf('(torrent id:%d)', $this->id); + } + + $and = $renderMode === self::ARTIST_DISPLAY_HTML ? '&' : '&'; + $chunk = []; + if ($djCount == 1) { + $chunk[] = $this->artistLink($roleList['dj'][0], $renderMode); + } elseif ($djCount == 2) { + $chunk[] = $this->artistLink($roleList['dj'][0], $renderMode) . " $and " . $this->artistLink($roleList['dj'][1], $renderMode); + } elseif ($djCount > 2) { + $chunk[] = 'Various DJs'; + } else { + if ($composerCount > 0) { + if ($composerCount == 1) { + $chunk[] = $this->artistLink($roleList['composer'][0], $renderMode); + } elseif ($composerCount == 2) { + $chunk[] = $this->artistLink($roleList['composer'][0], $renderMode) . " $and " . $this->artistLink($roleList['composer'][1], $renderMode); + } elseif ($composerCount > 2 && $mainCount + $conductorCount == 0) { + $chunk[] = 'Various Composers'; + } + if ($mainCount > 0) { + $chunk[] = 'Various Composers performed by'; + } + } + + if ($composerCount > 0 + && $mainCount > 1 + && $conductorCount > 1 + ) { + $chunk[] = 'Various Artists'; + } else { + if ($mainCount == 1) { + $chunk[] = $this->artistLink($roleList['main'][0], $renderMode); + } elseif ($mainCount == 2) { + $chunk[] = $this->artistLink($roleList['main'][0], $renderMode) . " $and " . $this->artistLink($roleList['main'][1], $renderMode); + } elseif ($mainCount > 2) { + $chunk[] = 'Various Artists'; + } + + if ($conductorCount > 0 + && $mainCount + $composerCount > 0 + && ($composerCount < 3 || $mainCount > 0) + ) { + $chunk[] = 'under'; + } + if ($conductorCount == 1) { + $chunk[] = $this->artistLink($roleList['conductor'][0], $renderMode); + } elseif ($conductorCount == 2) { + $chunk[] = $this->artistLink($roleList['conductor'][0], $renderMode) . " $and " . $this->artistLink($roleList['conductor'][1], $renderMode); + } elseif ($conductorCount > 2) { + $chunk[] = 'Various Conductors'; + } + } + } + return $nameCache[$renderMode][$this->id] = implode(' ', $chunk); + } + + public function torrentList(): array { + $key = sprintf(self::CACHE_TLIST_KEY, $this->id); + if (!$this->revisionId) { + $list = $this->cache->get_value($key); + if ($list !== false) { + return $list; + } + } + + $columns = " + t.ID, + t.Media, + t.Format, + t.Encoding, + t.Remastered, + t.RemasterYear, + t.RemasterTitle, + t.RemasterRecordLabel, + t.RemasterCatalogueNumber, + t.Scene, + t.HasLog, + t.HasCue, + t.HasLogDB, + t.LogScore, + t.LogChecksum, + t.FileCount, + t.Size, + tls.Seeders, + tls.Leechers, + tls.Snatched, + t.FreeTorrent, + t.Time, + t.Description, + t.FileList, + t.FilePath, + t.UserID, + tls.last_action, + HEX(t.info_hash) AS InfoHash, + tbt.TorrentID AS BadTags, + tbf.TorrentID AS BadFolders, + tfi.TorrentID AS BadFiles, + ml.TorrentID AS MissingLineage, + ca.TorrentID AS CassetteApproved, + lma.TorrentID AS LossymasterApproved, + lwa.TorrentID AS LossywebApproved, + t.LastReseedRequest, + group_concat(tl.LogID) as ripLogIds + "; + + $this->db->prepared_query(" + SELECT $columns, 0 as is_deleted + FROM torrents AS t + INNER JOIN torrents_leech_stats tls ON (tls.TorrentID = t.ID) + LEFT JOIN torrents_bad_tags AS tbt ON (tbt.TorrentID = t.ID) + LEFT JOIN torrents_bad_folders AS tbf ON (tbf.TorrentID = t.ID) + LEFT JOIN torrents_bad_files AS tfi ON (tfi.TorrentID = t.ID) + LEFT JOIN torrents_missing_lineage AS ml ON (ml.TorrentID = t.ID) + LEFT JOIN torrents_cassette_approved AS ca ON (ca.TorrentID = t.ID) + LEFT JOIN torrents_lossymaster_approved AS lma ON (lma.TorrentID = t.ID) + LEFT JOIN torrents_lossyweb_approved AS lwa ON (lwa.TorrentID = t.ID) + LEFT JOIN torrents_logs AS tl ON (tl.TorrentID = t.ID) + WHERE t.GroupID = ? + GROUP BY t.ID + UNION DISTINCT + SELECT $columns, 1 as is_deleted + FROM deleted_torrents AS t + INNER JOIN deleted_torrents_leech_stats tls ON (tls.TorrentID = t.ID) + LEFT JOIN deleted_torrents_bad_tags AS tbt ON (tbt.TorrentID = t.ID) + LEFT JOIN deleted_torrents_bad_folders AS tbf ON (tbf.TorrentID = t.ID) + LEFT JOIN deleted_torrents_bad_files AS tfi ON (tfi.TorrentID = t.ID) + LEFT JOIN deleted_torrents_missing_lineage AS ml ON (ml.TorrentID = t.ID) + LEFT JOIN deleted_torrents_cassette_approved AS ca ON (ca.TorrentID = t.ID) + LEFT JOIN deleted_torrents_lossymaster_approved AS lma ON (lma.TorrentID = t.ID) + LEFT JOIN deleted_torrents_lossyweb_approved AS lwa ON (lwa.TorrentID = t.ID) + LEFT JOIN torrents_logs AS tl ON (tl.TorrentID = t.ID) + WHERE t.GroupID = ? + GROUP BY t.ID + ORDER BY Remastered ASC, + (RemasterYear != 0) DESC, + RemasterYear ASC, + RemasterTitle ASC, + RemasterRecordLabel ASC, + RemasterCatalogueNumber ASC, + Media ASC, + Format, + Encoding, + ID + ", $this->id, $this->id + ); + $list = $this->db->to_array('ID', MYSQLI_ASSOC, false); + if (empty($list)) { + return []; + } + if (!$this->revisionId) { + $this->cache->cache_value($key, $list, in_array(0, $this->db->collect('Seeders')) ? 600 : 3600); + } + return $list; + } + + /** + * How many unresolved torrent reports are there in this group? + * @param int Group ID + * @return int number of unresolved reports + */ + public function unresolvedReportsTotal(): int { + return $this->db->scalar(" + SELECT count(*) + FROM reportsv2 AS r + INNER JOIN torrents AS t ON (t.ID = r.TorrentID) + WHERE r.Status != 'Resolved' + AND t.GroupID = ? + ", $this->id + ); + } + + public function expireToken(int $userId, int $torrentId): bool { + $hash = $this->db->scalar(" + SELECT info_hash FROM torrents WHERE ID = ? + ", $torrentId + ); + if (!$hash) { + return false; + } + $this->db->prepared_query(" + UPDATE users_freeleeches SET + Expired = true + WHERE UserID = ? + AND TorrentID = ? + ", $userId, $torrentId + ); + $this->cache->delete_value("users_tokens_{$userId}"); + (new \Gazelle\Tracker)->update_tracker('remove_token', ['info_hash' => rawurlencode($hash), 'userid' => $userId]); + return true; + } + + public function missingLogfiles(int $userId): array { + $this->db->prepared_query(" + SELECT ID, GroupID, `Format`, Encoding, HasCue, HasLog, HasLogDB, LogScore, LogChecksum + FROM torrents + WHERE HasLog = '1' AND HasLogDB = '0' AND UserID = ? + ", $userId + ); + if (!$this->db->has_results()) { + return []; + } + $GroupIDs = $this->db->collect('GroupID'); + $TorrentsInfo = $this->db->to_array('ID'); + $Groups = \Torrents::get_groups($GroupIDs); + + $result = []; + foreach ($TorrentsInfo as $TorrentID => $Torrent) { + [$ID, $GroupID, $Format, $Encoding, $HasCue, $HasLog, $HasLogDB, $LogScore, $LogChecksum] = $Torrent; + $Group = $Groups[$GroupID]; + $GroupName = $Group['Name']; + $GroupYear = $Group['Year']; + $ExtendedArtists = $Group['ExtendedArtists']; + $Artists = $Group['Artists']; + if (!empty($ExtendedArtists[1]) || !empty($ExtendedArtists[4]) || !empty($ExtendedArtists[5])) { + unset($ExtendedArtists[2]); + unset($ExtendedArtists[3]); + $DisplayName = \Artists::display_artists($ExtendedArtists); + } elseif (!empty($Artists)) { + $DisplayName = \Artists::display_artists([1 => $Artists]); + } else { + $DisplayName = ''; + } + $DisplayName .= ''.$GroupName.''; + if ($GroupYear > 0) { + $DisplayName .= " [{$GroupYear}]"; + } + $Info = []; + if (strlen($Format)) { + $Info[] = $Format; + } + if (strlen($Encoding)) { + $Info[] = $Encoding; + } + if (!empty($Info)) { + $DisplayName .= ' [' . implode('/', $Info) . ']'; + } + if ($HasLog == '1') { + $DisplayName .= ' / Log'.($HasLogDB == '1' ? " ({$LogScore}%)" : ""); + } + if ($HasCue == '1') { + $DisplayName .= ' / Cue'; + } + if ($LogChecksum == '0') { + $DisplayName .= ' / ' . \Format::torrent_label('Bad/Missing Checksum'); + } + $result[$ID] = $DisplayName; + } + return $result; + } +} diff --git a/app/Torrent.php b/app/Torrent.php index 104212b8c..18913d0d4 100644 --- a/app/Torrent.php +++ b/app/Torrent.php @@ -4,6 +4,19 @@ class Torrent extends BaseObject { + const CACHE_KEY = 't_%d'; + const CACHE_KEY_PEERLIST_TOTAL = 'peerlist_total_%d'; + const CACHE_KEY_PEERLIST_PAGE = 'peerlist_page_%d_%d'; + + const SNATCHED_UPDATE_INTERVAL = 3600; // How often we want to update users' snatch lists + + protected $isDeleted = false; + protected $showSnatched; + protected $snatchBucket; + protected $tokenCache; + protected $updateTime; + protected $viewerId; + public function tableName(): string { return 'torrents'; } @@ -14,4 +27,597 @@ public function __construct(int $id) { public function flush() { } + + /** + * Set the viewer context, for snatched indicators etc. + * + * @param int $userID The ID of the User + * @return $this to allow method chaining + */ + public function setViewerId(int $viewerId) { + $this->viewerId = $viewerId; + return $this; + } + + /** + * In the context of a user, determine whether snatched indicators should be + * added to torrent and group info. + * + * @param int $userID The ID of the User + * @return $this to allow method chaining + */ + public function setShowSnatched(int $showSnatched) { + $this->showSnatched = $showSnatched; + return $this; + } + + /** + * Check if the viewer has an active freeleech token on a torrent + * + * @param int userId + * @return true if an active token exists for the viewer + */ + public function hasToken(int $userId): bool { + if (!$this->tokenCache) { + $key = "users_tokens_" . $userId; + $this->tokenCache = $this->cache->get_value($key); + if ($this->tokenCache === false) { + $qid = $this->db->get_query_id(); + $this->db->prepared_query(" + SELECT TorrentID FROM users_freeleeches WHERE Expired = 0 AND UserID = ? + ", $userId + ); + $this->tokenCache = array_fill_keys($this->db->collect('TorrentID', false), true); + $this->db->set_query_id($qid); + $this->cache->cache_value($key, $this->tokenCache, 3600); + } + } + return isset($this->tokenCache[$this->id]); + } + + /** + * Get the metadata of the torrent + * + * @return array of many things + */ + public function info(): array { + $key = sprintf(self::CACHE_KEY, $this->id); + $info = $this->cache->get_value($key); + $info = false; + if ($info === false) { + $template = "SELECT t.GroupID, t.UserID, t.Media, t.Format, t.Encoding, + t.Remastered, t.RemasterYear, t.RemasterTitle, t.RemasterCatalogueNumber, t.RemasterRecordLabel, + t.Scene, t.HasLog, t.HasCue, t.HasLogDB, t.LogScore, t.LogChecksum, + hex(t.info_hash) as info_hash, t.FileCount, t.FileList, t.FilePath, t.Size, + t.FreeTorrent, t.FreeLeechType, t.Time, t.Description, t.LastReseedRequest, + tls.Seeders, tls.Leechers, tls.Snatched, tls.last_action, + tbt.TorrentID AS BadTags, tbf.TorrentID AS BadFolders, tfi.TorrentID AS BadFiles, ml.TorrentID AS MissingLineage, + ca.TorrentID AS CassetteApproved, lma.TorrentID AS LossyMasterApproved, lwa.TorrentID AS LossyWebApproved, + group_concat(tl.LogID) as ripLogIds + FROM %table% t + INNER JOIN torrents_leech_stats tls ON (tls.TorrentID = t.ID) + LEFT JOIN torrents_bad_tags AS tbt ON (tbt.TorrentID = t.ID) + LEFT JOIN torrents_bad_folders AS tbf ON (tbf.TorrentID = t.ID) + LEFT JOIN torrents_bad_files AS tfi ON (tfi.TorrentID = t.ID) + LEFT JOIN torrents_missing_lineage AS ml ON (ml.TorrentID = t.ID) + LEFT JOIN torrents_cassette_approved AS ca ON (ca.TorrentID = t.ID) + LEFT JOIN torrents_lossymaster_approved AS lma ON (lma.TorrentID = t.ID) + LEFT JOIN torrents_lossyweb_approved AS lwa ON (lwa.TorrentID = t.ID) + LEFT JOIN torrents_logs AS tl ON (tl.TorrentID = t.ID) + WHERE t.ID = ? + GROUP BY t.ID + "; + $info = $this->db->rowAssoc(str_replace('%table%', 'torrents', $template), $this->id); + if (is_null($info)) { + $info = $this->db->rowAssoc(str_replace('%table%', 'deleted_torrents', $template), $this->id); + $this->isDeleted = true; + } + if (is_null($info)) { + return []; + } + foreach (['last_action', 'LastReseedRequest', 'RemasterCatalogueNumber', 'RemasterRecordLabel', 'RemasterTitle', 'RemasterYear'] + as $nullable + ) { + $info[$nullable] = $info[$nullable] == '' ? null : $info[$nullable]; + } + foreach (['FreeTorrent', 'LogChecksum', 'HasCue', 'HasLog', 'HasLogDB', 'Remastered', 'Scene'] + as $zerotruth + ) { + $info[$zerotruth] = !($info[$zerotruth] == '0'); + } + foreach (['BadFiles', 'BadFolders', 'BadTags', 'CassetteApproved', 'LossyMasterApproved', 'LossyWebApproved', 'MissingLineage'] + as $emptytruth + ) { + $info[$emptytruth] = !($info[$emptytruth] == ''); + } + $info['ripLogIds'] = empty($info['ripLogIds']) ? [] : array_map('intval', explode(',', $info['ripLogIds'])); + $info['LogCount'] = count($info['ripLogIds']); + $info['FileList'] = explode("\n", $info['FileList']); + + $this->cache->cache_value($key, $info, ($info['Seeders'] ?? 0) > 0 ? 600 : 3600); + } + + if ($this->viewerId) { + $info['PersonalFL'] = !$info['FreeTorrent'] && $this->hasToken($this->viewerId); + $info['IsSnatched'] = $this->showSnatched && $this->isSnatched($this->viewerId); + } else { + $info['PersonalFL'] = false; + $info['IsSnatched'] = false; + } + + return $info; + } + + /** + * Group ID this torrent belongs to + * + * @return int group id + */ + public function groupId(): int { + return $this->info()['GroupID']; + } + + /** + * Get the torrent group in which this torrent belongs. + * + * @return TGroup group instance + */ + public function group(): TGroup { + return new TGroup($this->info()['GroupID']); + } + + /** + * The uploader of this torrent + * + * @return User uploader + */ + public function uploader(): User { + return new User($this->info()['UserID']); + } + + /** + * The infohash of this torrent + * + * @return string hexified infohash + */ + public function infohash(): string { + return $this->info()['info_hash']; + } + + /** + * Is this a remastered release? + * + * @return bool remastered + */ + public function isRemastered(): bool { + return $this->info()['Remastered']; + } + + /** + * Combine torrent media into a standardized file name + * + * @param array Torrent metadata + * @param bool whether to use .txt or .torrent as file extension + * @param int $MaxLength maximum file name length + * @return string file name with at most $MaxLength characters + */ + public function torrentFilename(bool $asText, int $MaxLength) { + $MaxLength -= strlen($this->id) + 1 + ($asText ? 4 : 8); + $info = $this->info(); + $group = $this->group(); + $artist = safeFilename($group->artistName()); + if ($info['Year'] > 0) { + $artist .= ".{$info['Year']}"; + } + $meta = []; + if ($info['Media'] != '') { + $meta[] = $info['Media']; + } + if ($info['Format'] != '') { + $meta[] = $info['Format']; + } + if ($info['Encoding'] != '') { + $meta[] = $info['Encoding']; + } + $label = empty($meta) ? '' : ('.(' . safeFilename(implode('-', $meta)) . ')'); + + $filename = safeFilename($group->name()); + if (!$filename) { + $filename = 'Unnamed'; + } elseif (mb_strlen("$artist.$filename$label", 'UTF-8') <= $MaxLength) { + $filename = "$artist.$filename"; + } + + $filename = shortenString($filename . $label, $MaxLength, true, false); + if ($info['TorrentID'] !== false) { + $filename .= "-{$info['TorrentID']}"; + } + return $asText ? "$filename.txt" : "$filename.torrent"; + } + + /** + * Convert a stored torrent into a binary file that can be loaded in a torrent client + * + * @param mixed $TorrentData bencoded torrent without announce URL + * @param string $AnnounceURL + * @param int $TorrentID + * @return string bencoded string + */ + public function torrentBody(string $announceUrl): string { + $filer = new \Gazelle\File\Torrent; + $contents = $filer->get($this->id); + if (is_null($contents)) { + return ''; + } + $tor = new \OrpheusNET\BencodeTorrent\BencodeTorrent; + $tor->decodeString($contents); + $tor->cleanDataDictionary(); + $tor->setValue([ + 'announce' => $announceUrl, + 'comment' => SITE_URL . "/torrents.php?torrentid=" . $this->id, + ]); + return $tor->getEncode(); + } + + public function modifyLogscore(): int { + $count = $this->db->scalar(" + SELECT count(*) FROM torrents_logs WHERE TorrentID = ? + ", $this->id + ); + if (!$count) { + $this->db->prepared_query(" + UPDATE torrents SET + HasLogDB = '0', + LogChecksum = '1', + LogScore = 0 + WHERE ID = ? + ", $this->id + ); + } else { + $this->db->prepared_query(" + UPDATE torrents AS t + LEFT JOIN ( + SELECT TorrentID, + min(CASE WHEN Adjusted = '1' THEN AdjustedScore ELSE Score END) AS Score, + min(CASE WHEN Adjusted = '1' THEN AdjustedChecksum ELSE Checksum END) AS Checksum + FROM torrents_logs + WHERE TorrentID = ? + GROUP BY TorrentID + ) AS tl ON (t.ID = tl.TorrentID) + SET + t.LogScore = tl.Score, + t.LogChecksum = tl.Checksum + WHERE t.ID = ? + ", $this->id, $this->id + ); + } + $this->cache->deleteMulti(["torrent_group_" . $this->groupId(), "torrents_details_" . $this->groupId()]); + return $this->db->affected_rows(); + } + + public function adjustLogscore(int $logId, $adjusted, int $adjScore, $adjChecksum, int $adjBy, $adjReason, array $adjDetails): int { + $this->db->prepared_query(" + UPDATE torrents_logs SET + Adjusted = ?, AdjustedScore = ?, AdjustedChecksum = ?, AdjustedBy = ?, AdjustmentReason = ?, AdjustmentDetails = ? + WHERE TorrentID = ? AND LogID = ? + ", $adjusted, $adjScore, $adjChecksum, $adjBy, $adjReason, serialize($adjDetails), + $this->id, $logId + ); + if ($this->db->affected_rows() > 0) { + return $this->modifyLogscore(); + } + return 0; + } + + public function rescoreLog(int $logId, \Gazelle\Logfile $logfile, string $version): int { + $this->db->prepared_query(" + UPDATE torrents_logs SET + Score = ?, `Checksum` = ?, ChecksumState = ?, Ripper = ?, RipperVersion = ?, + `Language` = ?, Details = ?, LogcheckerVersion = ?, + Adjusted = '0' + WHERE TorrentID = ? AND LogID = ? + ", $logfile->score(), $logfile->checksumStatus(), $logfile->checksumState(), $logfile->ripper(), $logfile->ripperVersion(), + $logfile->language(), $logfile->detailsAsString(), $version, + $this->id, $logId + ); + if ($this->db->affected_rows() > 0) { + return $this->modifyLogscore(); + } + return 0; + } + + /** + * Has the viewing user snatched this torrent? (And do they want + * to know about it?) + * + * @param int user id + * @return bool viewer has snatched. + */ + public function isSnatched(int $userId): bool { + $buckets = 64; + $bucketMask = $buckets - 1; + $bucketId = $this->id & $bucketMask; + + $snatchKey = "users_snatched_" . $userId . "_time"; + if (!$this->snatchBucket) { + $this->snatchBucket = array_fill(0, $buckets, false); + $this->updateTime = $this->cache->get_value($snatchKey); + if ($this->updateTime === false) { + $this->updateTime = [ + 'last' => 0, + 'next' => 0 + ]; + } + } elseif (isset($this->snatchBucket[$bucketId][$this->id])) { + return true; + } + + // Torrent was not found in the previously inspected snatch lists + $bucket =& $this->snatchBucket[$bucketId]; + if ($bucket === false) { + $now = time(); + // This bucket hasn't been checked before + $bucket = $this->cache->get_value($snatchKey, true); + if ($bucket === false || $now > $this->updateTime['next']) { + $bucketKeyStem = 'users_snatched_' . $userId . '_'; + $updated = []; + $qid = $this->db->get_query_id(); + if ($bucket === false || $this->updateTime['last'] == 0) { + for ($i = 0; $i < $buckets; $i++) { + $this->snatchBucket[$i] = []; + } + // Not found in cache. Since we don't have a suitable index, it's faster to update everything + $this->db->prepared_query(" + SELECT fid FROM xbt_snatched WHERE uid = ? + ", $userId + ); + while ([$id] = $this->db->next_record(MYSQLI_NUM, false)) { + $this->snatchBucket[$id & $bucketMask][(int)$id] = true; + } + $updated = array_fill(0, $buckets, true); + } elseif (isset($bucket[$this->id])) { + // Old cache, but torrent is snatched, so no need to update + return true; + } else { + // Old cache, check if torrent has been snatched recently + $this->db->prepared_query(" + SELECT fid FROM xbt_snatched WHERE uid = ? AND tstamp >= ? + ", $userId, $this->updateTime['last'] + ); + while ([$id] = $this->db->next_record(MYSQLI_NUM, false)) { + $bucketId = $id & $bucketMask; + if ($this->snatchBucket[$bucketId] === false) { + $this->snatchBucket[$bucketId] = $this->cache->get_value("$bucketKeyStem$bucketId", true); + if ($this->snatchBucket[$bucketId] === false) { + $this->snatchBucket[$bucketId] = []; + } + } + $this->snatchBucket[$bucketId][(int)$id] = true; + $updated[$bucketId] = true; + } + } + $this->db->set_query_id($qid); + for ($i = 0; $i < $buckets; $i++) { + if (isset($updated[$i])) { + $this->cache->cache_value("$bucketKeyStem$i", $this->snatchBucket[$i], 7200); + } + } + $this->updateTime['last'] = $now; + $this->updateTime['next'] = $now + self::SNATCHED_UPDATE_INTERVAL; + $this->cache->cache_value($snatchKey, $this->updateTime, 7200); + } + } + return isset($bucket[$this->id]); + } + + /** + * Remove a torrent. + * + * @param int userid Who is removing the torrent + * @param string reason Why is this being deleted? (For the log) + * @param string trackerReason The deletion reason for ocelot to report to users. + */ + public function remove(int $userId, string $reason, int $trackerReason = -1): array { + $qid = $this->db->get_query_id(); + $info = $this->info(); + if ($this->id > MAX_PREV_TORRENT_ID) { + (new \Gazelle\Bonus)->removePointsForUpload($info['UserID'], + [$info['Format'], $info['Media'], $info['Encoding'], $info['HasLogDB'], $info['LogScore'], $info['LogChecksum']]); + } + + $manager = new \Gazelle\DB; + $manager->relaxConstraints(true); + [$ok, $message] = $manager->softDelete(SQLDB, 'torrents_leech_stats', [['TorrentID', $this->id]], false); + if (!$ok) { + return [false, $message]; + } + [$ok, $message] = $manager->softDelete(SQLDB, 'torrents', [['ID', $this->id]]); + if (!$ok) { + return [false, $message]; + } + $infohash = $this->infohash(); + $manager->relaxConstraints(false); + (new \Gazelle\Tracker)->update_tracker('delete_torrent', [ + 'id' => $this->id, + 'info_hash' => rawurlencode(hex2bin($infohash)), + 'reason' => $trackerReason, + ]); + $this->cache->decrement('stats_torrent_count'); + + $group = $this->group(); + $groupId = $group->id(); + $Count = $this->db->scalar(" + SELECT count(*) FROM torrents WHERE GroupID = ? + ", $groupId + ); + if ($Count > 0) { + \Torrents::update_hash($groupId); + } + + $manager->softDelete(SQLDB, 'torrents_files', [['TorrentID', $this->id]]); + $manager->softDelete(SQLDB, 'torrents_bad_files', [['TorrentID', $this->id]]); + $manager->softDelete(SQLDB, 'torrents_bad_folders', [['TorrentID', $this->id]]); + $manager->softDelete(SQLDB, 'torrents_bad_tags', [['TorrentID', $this->id]]); + $manager->softDelete(SQLDB, 'torrents_cassette_approved', [['TorrentID', $this->id]]); + $manager->softDelete(SQLDB, 'torrents_lossymaster_approved', [['TorrentID', $this->id]]); + $manager->softDelete(SQLDB, 'torrents_lossyweb_approved', [['TorrentID', $this->id]]); + $manager->softDelete(SQLDB, 'torrents_missing_lineage', [['TorrentID', $this->id]]); + + $this->db->prepared_query(" + INSERT INTO user_torrent_remove + (user_id, torrent_id) + VALUES (?, ?) + ", $userId, $this->id + ); + + // Tells Sphinx that the group is removed + $this->db->prepared_query(" + REPLACE INTO sphinx_delta + (ID, Time) + VALUES (?, now()) + ", $this->id + ); + + $this->db->prepared_query(" + UPDATE reportsv2 SET + Status = 'Resolved', + LastChangeTime = now(), + ModComment = 'Report already dealt with (torrent deleted)' + WHERE Status != 'Resolved' + AND TorrentID = ? + ", $this->id + ); + $count = $this->db->affected_rows(); + if ($count) { + $this->cache->decrement('num_torrent_reportsv2', $count); + } + + // Torrent notifications + $this->db->prepared_query(" + SELECT concat('user_notify_upload_', UserID) as ck + FROM users_notify_torrents + WHERE TorrentID = ? + ", $this->id + ); + $deleteKeys = $this->db->collect('ck', false); + $manager->softDelete(SQLDB, 'users_notify_torrents', [['TorrentID', $this->id]]); + + if ($userId !== 0) { + $RecentUploads = $this->cache->get_value("user_recent_up_" . $userId); + if (is_array($RecentUploads)) { + foreach ($RecentUploads as $Key => $Recent) { + if ($Recent['ID'] == $groupId) { + $deleteKeys[] = "user_recent_up_" . $userId; + break; + } + } + } + } + + $deleteKeys[] = "torrent_download_" . $this->id; + $deleteKeys[] = "torrent_group_" . $groupId; + $deleteKeys[] = "torrents_details_" . $groupId; + $this->cache->deleteMulti($deleteKeys); + + $sizeMB = number_format($this->info()['Size'] / (1024 * 1024), 2) . ' MiB'; + $username = $userId ? (new Manager\User)->findById($userId)->username() : 'system'; + (new Log)->general( + "Torrent " + . $this->id + . " (" . $group->name() . ") [" . (new Manager\TorrentLabel)->load($this->info())->release() + . "] ($sizeMB $infohash) was deleted by $username for reason: $reason" + ) + ->torrent( + $groupId, $this->id, $userId, + "deleted torrent ($sizeMB $infohash) for reason: $reason" + ); + + $this->db->set_query_id($qid); + return [true, "torrent " . $this->id . " removed"]; + } + + public function expireToken(int $userId): bool { + $hash = $this->db->scalar(" + SELECT info_hash FROM torrents WHERE ID = ? + ", $this->id + ); + if (!$hash) { + return false; + } + $this->db->prepared_query(" + UPDATE users_freeleeches SET + Expired = true + WHERE UserID = ? + AND TorrentID = ? + ", $userId, $this->id + ); + $this->cache->delete_value("users_tokens_{$userId}"); + (new \Gazelle\Tracker)->update_tracker('remove_token', ['info_hash' => rawurlencode($hash), 'userid' => $userId]); + return true; + } + + /** + * Get the requests filled by this torrent. + * (Should only be one, but hey, who knows what the original developer was looking to catch?) + * @param int torrent ID + * @return DB object to loop over [request id, filler user id, date filled] + */ + public function requestFills(): array { + $this->db->prepared_query(" + SELECT r.ID, r.FillerID, r.TimeFilled FROM requests AS r WHERE r.TorrentID = ? + ", $this->id + ); + return $this->db->to_array(false, MYSQLI_NUM, false); + } + + public function peerlistTotal() { + $key = sprintf(self::CACHE_KEY_PEERLIST_TOTAL, $this->id); + if (($total = $this->cache->get_value($key)) === false) { + // force flush the first page of results + $this->cache->delete_value(sprintf(self::CACHE_KEY_PEERLIST_PAGE, $this->id, 0)); + $total = $this->db->scalar(" + SELECT count(*) + FROM xbt_files_users AS xfu + INNER JOIN users_main AS um ON (um.ID = xfu.uid) + INNER JOIN torrents AS t ON (t.ID = xfu.fid) + WHERE um.Visible = '1' + AND xfu.fid = ? + ", $this->id + ); + $this->cache->cache_value($key, $total, 300); + } + return $total; + } + + public function peerlistPage(int $userId, int $limit, int $offset) { + $key = sprintf(self::CACHE_KEY_PEERLIST_PAGE, $this->id, $offset); + if (($list = $this->cache->get_value($key)) === false) { + // force flush the next page of results + $this->cache->delete_value(sprintf(self::CACHE_KEY_PEERLIST_PAGE, $this->id, $offset + $limit)); + $this->db->prepared_query(" + SELECT + xfu.active, + xfu.connectable, + xfu.remaining, + xfu.uploaded, + xfu.useragent, + xfu.ip AS ipv4addr, + xfu.uid AS user_id, + t.Size AS size, + sx.name AS seedbox + FROM xbt_files_users AS xfu + INNER JOIN users_main AS um ON (um.ID = xfu.uid) + INNER JOIN torrents AS t ON (t.ID = xfu.fid) + LEFT JOIN user_seedbox sx ON (xfu.ip = inet_ntoa(sx.ipaddr) AND xfu.useragent = sx.useragent AND xfu.uid = ?) + WHERE um.Visible = '1' + AND xfu.fid = ? + ORDER BY xfu.uid = ? DESC, xfu.uploaded DESC + LIMIT ? OFFSET ? + ", $userId, $this->id, $userId, $limit, $offset + ); + $list = $this->db->to_array(false, MYSQLI_ASSOC); + $this->cache->cache_value($key, $list, 300); + } + return $list; + } } diff --git a/app/Torrent/Reaper.php b/app/Torrent/Reaper.php index e1fff742c..f9c389da8 100644 --- a/app/Torrent/Reaper.php +++ b/app/Torrent/Reaper.php @@ -30,21 +30,22 @@ public function deleteDeadTorrents(bool $unseeded, bool $neverSeeded) { $logEntries = $deleteNotes = []; $torMan = new \Gazelle\Manager\Torrent; - $torMan->setArtistDisplayText()->setViewer(0); $labelMan = new \Gazelle\Manager\TorrentLabel; $labelMan->showMedia(true)->showEdition(true); $i = 0; foreach ($torrents as $id) { - [$group, $torrent] = $torMan->setTorrentId($id)->torrentInfo(); + $t = $torMan->findById($id); + $torrent = $t->info(); + $group = $t->group->info(); $name = $group['Name'] . " " . $labelMan->load($torrent)->edition(); - $artistName = $torMan->artistName(); + $artistName = $t->group()->artistName(); if ($artistName) { $name = "$artistName - $name"; } - [$success, $message] = $torMan->remove('inactivity (unseeded)'); + [$success, $message] = $t->remove('inactivity (unseeded)'); if (!$success) { continue; } diff --git a/app/Tracker.php b/app/Tracker.php index bda6aa2d2..ce4c5d164 100644 --- a/app/Tracker.php +++ b/app/Tracker.php @@ -38,6 +38,9 @@ public function requestList(): array { * @param boolean $ToIRC Sends a message to the channel #tracker with the GET URL. */ public function update_tracker($Action, $Updates, $ToIRC = false) { + if (DISABLE_TRACKER) { + return true; + } // Build request $Get = TRACKER_SECRET . "/update?action=$Action"; foreach ($Updates as $Key => $Value) { diff --git a/app/User.php b/app/User.php index ca85c4508..44b86cca3 100644 --- a/app/User.php +++ b/app/User.php @@ -2214,11 +2214,12 @@ public function tokenPage(int $limit, int $offset): array { $name = "(Deleted torrent {$t['torrent_id']})"; } else { $name = "{$t['group_name']}"; - $artist = $this->torMan->setGroupId($t['group_id'])->setTorrentId($t['torrent_id'])->artistHtml(); + $torrent = $this->torMan->findById($t['torrent_id']); + $artist = $torrent->group()->artistHtml(); if ($artist) { $name = "$artist - $name"; } - $this->labelMan->load($this->torMan->torrentInfo()[1]); + $this->labelMan->load($torrent->info()); $name .= ' [' . $this->labelMan->label() . ']'; } $t['expired'] = ($t['expired'] === 1); diff --git a/boris b/boris index 9c9f68c7e..363365b24 100755 --- a/boris +++ b/boris @@ -49,6 +49,7 @@ $preload = [ 'collMan' => new \Gazelle\Manager\Collage, 'forMan' => new \Gazelle\Manager\Forum, 'tagMan' => new \Gazelle\Manager\Tag, + 'tgMan' => new \Gazelle\Manager\TGroup, 'torMan' => new \Gazelle\Manager\Torrent, 'userMan' => new \Gazelle\Manager\User, ]; diff --git a/classes/config.template.php b/classes/config.template.php index ceb1637a1..ae3072864 100644 --- a/classes/config.template.php +++ b/classes/config.template.php @@ -446,6 +446,8 @@ 'g5' => 'torrent_group_%d', 'g6' => 'torrent_group_light_%d', 'g7' => 'groups_artists_%d', + 'g8' => 'tg_%d', + 'g9' => 'tlist_%d', ], 'user' => [ 'u1' => 'bookmarks_group_ids_%d', diff --git a/classes/torrents.class.php b/classes/torrents.class.php index 84a694568..baa45213f 100644 --- a/classes/torrents.class.php +++ b/classes/torrents.class.php @@ -436,6 +436,8 @@ public static function delete_group($GroupID) { $Cache->delete_value("torrents_details_$GroupID"); $Cache->delete_value("torrent_group_$GroupID"); + $Cache->delete_value("tg_$GroupID"); + $Cache->delete_value("tlist_$GroupID"); $Cache->delete_value("groups_artists_$GroupID"); $DB->set_query_id($QueryID); } diff --git a/classes/util.php b/classes/util.php index e3e0ce97e..9be76b2df 100644 --- a/classes/util.php +++ b/classes/util.php @@ -846,8 +846,7 @@ function get_group_info($GroupID, $RevisionID = 0, $PersonalProperties = true, $ } function get_torrent_info($TorrentID, $RevisionID = 0, $PersonalProperties = true, $ApiCall = false) { - $torMan = new \Gazelle\Manager\Torrent; - $GroupInfo = get_group_info($torMan->idToGroupId($TorrentID), $RevisionID, $PersonalProperties, $ApiCall); + $GroupInfo = get_group_info((new Gazelle\Manager\Torrent)->findById($TorrentID)->id(), $RevisionID, $PersonalProperties, $ApiCall); if (!$GroupInfo) { return null; } diff --git a/gazelle.php b/gazelle.php index 1ff005827..ea3e70342 100644 --- a/gazelle.php +++ b/gazelle.php @@ -9,8 +9,7 @@ $Document = $PathInfo['filename']; if ($PathInfo['dirname'] !== '/') { - var_dump($PathInfo); - die(); header("Location: /index.php"); + exit; } elseif (in_array($Document, ['announce', 'scrape'])) { die("d14:failure reason40:Invalid .torrent, try downloading again.e"); } diff --git a/sections/ajax/riplog.php b/sections/ajax/riplog.php index 2ad4dbbe2..04eee5d69 100644 --- a/sections/ajax/riplog.php +++ b/sections/ajax/riplog.php @@ -4,7 +4,7 @@ if (!$logId) { json_error('missing logid parameter'); } -$torrent = (new Gazelle\Manager\Torrent)->findTorrentById((int)$_GET['id']); +$torrent = (new Gazelle\Manager\Torrent)->findById((int)($_GET['id'] ?? 0)); if (is_null($torrent)) { json_error('torrent not found'); } diff --git a/sections/ajax/torrent.php b/sections/ajax/torrent.php index dd97211b9..3ac65beed 100644 --- a/sections/ajax/torrent.php +++ b/sections/ajax/torrent.php @@ -9,15 +9,15 @@ $json->failure('bad parameters'); exit; } elseif ($torrentHash) { - if (!$json->setIdFromHash($torrentHash)) { + if (!$json->findByInfohash($torrentHash)) { exit; } } else { - if (!$json->setId($torrentId)) { + if (!$json->findById($torrentId)) { exit; } } $json->setVersion(5) - ->setViewer($LoggedUser['ID']) + ->setViewerId($LoggedUser['ID']) ->emit(); diff --git a/sections/ajax/torrentgroup.php b/sections/ajax/torrentgroup.php index 4d22306d1..3184580a9 100644 --- a/sections/ajax/torrentgroup.php +++ b/sections/ajax/torrentgroup.php @@ -4,28 +4,23 @@ $TorrentAllowed = ['ID', 'Media', 'Format', 'Encoding', 'Remastered', 'RemasterYear', 'RemasterTitle', 'RemasterRecordLabel', 'RemasterCatalogueNumber', 'Scene', 'HasLog', 'HasCue', 'LogScore', 'FileCount', 'Size', 'Seeders', 'Leechers', 'Snatched', 'FreeTorrent', 'Time', 'Description', 'FileList', 'FilePath', 'UserID', 'Username']; $GroupID = (int)$_GET['id']; -$TorrentHash = (string)$_GET['hash']; - -if ($GroupID && $TorrentHash) { +$infohash = $_GET['hash']; +if ($GroupID && $infohash) { json_die("failure", "bad parameters"); } - -$torMan = new \Gazelle\Manager\Torrent; - -if ($TorrentHash) { - if (!$torMan->isValidHash($TorrentHash)) { +$tgroupMan = new Gazelle\Manager\TGroup; +if ($GroupID) { + $group = $tgroupMan->findById($GroupID); +} else if ($infohash) { + $group = $tgroupMan->findByTorrentInfohash($infohash); + if (!$GroupID) { json_die("failure", "bad hash parameter"); - } else { - $GroupID = $torMan->hashToGroupId($TorrentHash); - if (!$GroupID) { - json_die("failure", "bad hash parameter"); - } } } - -if ($GroupID <= 0) { +if (is_null($group)) { json_die("failure", "bad id parameter"); } +$GroupID = $group->id(); $TorrentCache = get_group_info($GroupID, 0, true, true); if (!$TorrentCache) { diff --git a/sections/ajax/torrentgroupalbumart.php b/sections/ajax/torrentgroupalbumart.php index 69964394f..d0fdce922 100644 --- a/sections/ajax/torrentgroupalbumart.php +++ b/sections/ajax/torrentgroupalbumart.php @@ -1,10 +1,10 @@ setShowSnatched(false)->groupInfo((int)$_GET['id']); -if (!$info) { +$tgroup = (new Gazelle\Manager\TGroup)->findById((int)$_GET['id']); +if (is_null($tgroup)) { json_die('failure', 'bad id parameter'); } json_print("success", [ - 'wikiImage' => $info['WikiImage'] + 'wikiImage' => $tgroup->info()['WikiImage'], ]); diff --git a/sections/apply/apply.php b/sections/apply/apply.php index 3f3233633..a8ece2679 100644 --- a/sections/apply/apply.php +++ b/sections/apply/apply.php @@ -2,7 +2,6 @@ $appMan = new Gazelle\Manager\Applicant; if (isset($_POST['auth'])) { authorize(); -var_dump($_POST); $roleId = (int)($_POST['role'] ?? 0); $body = trim($_POST['body'] ?? ''); if (!$roleId) { diff --git a/sections/collages/torrent_collage.php b/sections/collages/torrent_collage.php index 83537f579..e46f81489 100644 --- a/sections/collages/torrent_collage.php +++ b/sections/collages/torrent_collage.php @@ -53,7 +53,12 @@ if (check_perms('zip_downloader')) { if (isset($LoggedUser['Collector'])) { [$ZIPList, $ZIPPrefs] = $LoggedUser['Collector']; - $ZIPList = explode(':', $ZIPList); + if (is_null($ZIPList)) { + $ZIPList = ['00', '11']; + $ZIPPrefs = 1; + } else { + $ZIPList = explode(':', $ZIPList); + } } else { $ZIPList = ['00', '11']; $ZIPPrefs = 1; diff --git a/sections/index/private.php b/sections/index/private.php index 71215a8bb..2d0820d7b 100644 --- a/sections/index/private.php +++ b/sections/index/private.php @@ -5,7 +5,7 @@ $forumMan = new Gazelle\Manager\Forum; $newsMan = new Gazelle\Manager\News; $newsReader = new Gazelle\WitnessTable\UserReadNews; -$torMan = new Gazelle\Manager\Torrent; +$tgroupMan = new Gazelle\Manager\TGroup; $userMan = new Gazelle\Manager\User; $viewer = new Gazelle\User($LoggedUser['ID']); @@ -47,8 +47,8 @@ 'blog' => new Gazelle\Manager\Blog, 'collage_count' => (new Gazelle\Stats\Collage)->collageCount(), 'leaderboard' => $leaderboard, - 'featured_aotm' => $torMan->featuredAlbumAotm(), - 'featured_showcase' => $torMan->featuredAlbumShowcase(), + 'featured_aotm' => $tgroupMan->featuredAlbumAotm(), + 'featured_showcase' => $tgroupMan->featuredAlbumShowcase(), 'staff_blog' => new Gazelle\Manager\StaffBlog, 'poll' => $poll, 'poll_thread_id' => $threadId, @@ -63,7 +63,7 @@ echo $Twig->render('index/private-main.twig', [ 'admin' => $viewer->permitted('admin_manage_news'), 'contest' => $contestMan->currentContest(), - 'latest' => $torMan->latestUploads(5), + 'latest' => $tgroupMan->latestUploads(5), 'news' => $newsMan->headlines(), ]); View::show_footer(['disclaimer'=>true]); diff --git a/sections/logchecker/take_upload.php b/sections/logchecker/take_upload.php index 974808e02..e430c764a 100644 --- a/sections/logchecker/take_upload.php +++ b/sections/logchecker/take_upload.php @@ -77,7 +77,7 @@ ", $logfileSummary->overallScore(), $logfileSummary->checksumStatus(), $TorrentID ); -$Cache->deleteMulti(["torrent_group_{$GroupID}", "torrents_details_{$GroupID}"]); +$Cache->deleteMulti(["torrent_group_{$GroupID}", "torrents_details_{$GroupID}", "tg_{$GroupID}", "tlist_{$GroupID}"]); View::show_header(); ?> diff --git a/sections/reportsv2/report.php b/sections/reportsv2/report.php index b67c5307b..98de5df8c 100644 --- a/sections/reportsv2/report.php +++ b/sections/reportsv2/report.php @@ -5,6 +5,7 @@ */ function build_torrents_table($GroupID, $GroupName, $GroupCategoryID, $ReleaseType, $TorrentList, $Types) { + // TODO: replace this horror with Twig global $Cache, $DB, $LoggedUser, $Twig; $torMan = new Gazelle\Manager\Torrent; @@ -14,14 +15,15 @@ function build_torrents_table($GroupID, $GroupName, $GroupCategoryID, $ReleaseTy $LastRemasterCatalogueNumber = ''; $EditionID = 0; - foreach ($TorrentList as $Torrent) { + $torMan = new Gazelle\Manager\Torrent; + // foreach ($TorrentList as $Torrent) { [$TorrentID, $Media, $Format, $Encoding, $Remastered, $RemasterYear, $RemasterTitle, $RemasterRecordLabel, $RemasterCatalogueNumber, $Scene, $HasLog, $HasCue, $HasLogDB, $LogScore, $LogChecksum, $FileCount, $Size, $Seeders, $Leechers, $Snatched, $FreeTorrent, $TorrentTime, $Description, $FileList, $FilePath, $UserID, $LastActive, $InfoHash, $BadTags, $BadFolders, $BadFiles, $MissingLineage, $CassetteApproved, $LossymasterApproved, $LossywebApproved, $LastReseedRequest, - $HasFile, $LogCount, $PersonalFL, $IsSnatched] = array_values($Torrent); + $HasFile, $LogCount, $PersonalFL, $IsSnatched] = array_values($TorrentList); $FirstUnknown = ($Remastered && !$RemasterYear); @@ -156,9 +158,10 @@ function build_torrents_table($GroupID, $GroupName, $GroupCategoryID, $ReleaseTy ) { $EditionID++; + $info = $torMan->findById($TorrentID)->info(); ?> - + groupid_ edition_ group_torrent" style="font-weight: normal;" id="torrent"> render('torrent/action.twig', [ - 'can_fl' => Torrents::can_use_token($Torrent), + 'can_fl' => Torrents::can_use_token($TorrentList), 'key' => $LoggedUser['torrent_pass'], - 't' => $Torrent, + 't' => $TorrentList, 'edit' => $CanEdit, 'remove' => check_perms('torrents_delete') || $UserID == $LoggedUser['ID'], 'pl' => true, @@ -239,7 +242,7 @@ function build_torrents_table($GroupID, $GroupName, $GroupCategoryID, $ReleaseTy groupInfo($GroupID); - $TorrentList = [$TorrentList[$TorrentID]]; + $torrent = (new Gazelle\Manager\Torrent)->findById($TorrentID); + $TorrentList = $torrent->info(); + $GroupDetails = $torrent->group()->info(); // Group details [$WikiBody,, $GroupID, $GroupName, $GroupYear,,, $ReleaseType, $GroupCategoryID,, $GroupVanityHouse,,,,,, $GroupFlags] = array_values($GroupDetails); diff --git a/sections/reportsv2/takeresolve.php b/sections/reportsv2/takeresolve.php index 897f0e5ad..6511f5798 100644 --- a/sections/reportsv2/takeresolve.php +++ b/sections/reportsv2/takeresolve.php @@ -160,9 +160,9 @@ . ($_POST['resolve_type'] == 'custom' ? '' : ' for the reason: ' . $ResolveType['title'] . ".") . ($logMessage ? " $logMessage" : ''); (new Gazelle\Manager\Torrent) - ->setTorrentId($torrentId) - ->setViewer($LoggedUser['ID']) + ->findById($torrentId) ->remove( + $LoggedUser['ID'], sprintf('%s (%s)', $ResolveType['title'], $logMessage ?? 'none'), $ResolveType['reason'] ); diff --git a/sections/tools/sandboxes/notification.php b/sections/tools/sandboxes/notification.php index 56a3be16d..5abd118fc 100644 --- a/sections/tools/sandboxes/notification.php +++ b/sections/tools/sandboxes/notification.php @@ -16,8 +16,10 @@ $torMan = new Gazelle\Manager\Torrent; if (isset($_POST['torrentid'])) { - $torMan->setTorrentId((int)$_POST['torrentid']); - [$group, $torrent] = $torMan->torrentInfo(); + $t = $torMan->findById((int)$_POST['torrentid']); + $torrent = $t->info(); + $tgroup = $t->group(); + $group = $tgroup->info(); if ($group) { $tags = explode('|', $group['tagNames']); if (!$tags) { @@ -30,7 +32,7 @@ ->addEncodings($torrent['Encoding']) ->addMedia($torrent['Media']) ->addYear($group['Year'], $torrent['RemasterYear']) - ->addArtists($torMan->artistRole()) + ->addArtists($tgroup->artistRole()) ->addTags($tags) ->addCategory($category) ->addUser(new Gazelle\User($torrent['UserID'])) diff --git a/sections/torrents/browse.php b/sections/torrents/browse.php index cb886b82f..d937eec57 100644 --- a/sections/torrents/browse.php +++ b/sections/torrents/browse.php @@ -2,6 +2,14 @@ use Gazelle\Util\SortableTableHeader; +if (!empty($_GET['searchstr']) || !empty($_GET['groupname'])) { + $t = (new Gazelle\Manager\Torrent)->findByInfohash($_GET['searchstr'] ?? $_GET['groupname']); + if ($t) { + header("Location: torrents.php?id=" . $t->groupId() . "&torrentid=" . $t->id()); + exit; + } +} + $user = new Gazelle\User($LoggedUser['ID']); $iconUri = STATIC_SERVER . '/styles/' . $LoggedUser['StyleName'] . '/images'; @@ -17,19 +25,6 @@ $header = new SortableTableHeader('time', $headerMap); $headerIcons = new SortableTableHeader('time', $headerMap, ['asc' => '', 'desc' => '']); -if (!empty($_GET['searchstr']) || !empty($_GET['groupname'])) { - $InfoHash = (!empty($_GET['searchstr'])) ? $_GET['searchstr'] : $_GET['groupname']; - - $torMan = new Gazelle\Manager\Torrent; - // Search by infohash - if ($InfoHash = $torMan->isValidHash($InfoHash)) { - if (list($ID, $GroupID) = $torMan->hashToTorrentGroup($InfoHash)) { - header("Location: torrents.php?id=$GroupID&torrentid=$ID"); - die(); - } - } -} - // Setting default search options if (!empty($_GET['setdefault'])) { $UnsetList = ['page', 'setdefault']; diff --git a/sections/torrents/delete.php b/sections/torrents/delete.php index b7d44359f..f92e8a373 100644 --- a/sections/torrents/delete.php +++ b/sections/torrents/delete.php @@ -169,6 +169,7 @@ $BBName .= " [url={$torrentUrl}]" . $mastering . "[/url]" . ($HasLog ? (" [url=$viewLogUrl]{$log}[/url]") : '') . $sizeMB; $torMan = new Gazelle\Manager\Torrent; + $userMan = new Gazelle\Manager\User; ?>
@@ -199,7 +200,8 @@ uploaded by
unresolvedGroupReports($GroupID); + $torrent = $torMan->findById($TorrentID); + $GroupOthers = $torrent->group()->unresolvedReportsTotal(); if ($GroupOthers > 0) { ?>
@@ -207,7 +209,7 @@
unresolvedUserReports($UploaderID); + $UploaderOthers = $userMan->unresolvedReportsTotal($UploaderID); if ($UploaderOthers > 0) { ?>
@@ -215,7 +217,7 @@
requestFills($TorrentID); + $requests = $torrent->requestFills(); foreach ($requests as $r) { [$RequestID, $FillerID, $FilledTime] = $r; $FillerName = (new Gazelle\User($FillerID))->username(); diff --git a/sections/torrents/delete_log.php b/sections/torrents/delete_log.php index 2e51d00f2..0f0e80f03 100644 --- a/sections/torrents/delete_log.php +++ b/sections/torrents/delete_log.php @@ -1,21 +1,19 @@ scalar('SELECT GroupID FROM torrents WHERE ID = ?', $torrentId); -if (!$groupId) { +$torrent = (new Gazelle\Manager\Torrent)->findById((int)$_GET['torrentid']); +$logId = (int)$_GET['logid']; +if (is_null($torrent) || !$logId) { error(404); } -(new Gazelle\File\RipLog)->remove([$torrentId, $logId]); -(new Gazelle\Log)->torrent($groupId, $torrentId, $LoggedUser['ID'], "Riplog ID $logId removed from torrent $torrentId"); +(new Gazelle\File\RipLog)->remove([$torrent->id(), $logId]); +(new Gazelle\Log)->torrent($torrent->groupId(), $torrent->id(), $LoggedUser['ID'], "Riplog ID $logId removed from torrent $torrentId"); -$torMan = new Gazelle\Manager\Torrent; -Torrents::clear_log($torrentId, $logId); -$torMan->modifyLogscore($groupId, $torrentId); +Torrents::clear_log($torrent->id(), $logId); +$torrent->modifyLogscore(); -header("Location: torrents.php?torrentid={$torrentId}"); +header("Location: torrents.php?torrentid=" . $torrent->id()); diff --git a/sections/torrents/download.php b/sections/torrents/download.php index 0631493cf..d531abbf4 100644 --- a/sections/torrents/download.php +++ b/sections/torrents/download.php @@ -158,9 +158,9 @@ } $Cache->delete_value('user_rlim_' . $userId); -$torMan = new Gazelle\Manager\Torrent; +$torrent = (new Gazelle\Manager\Torrent)->findById($torrentId); $downloadAsText = ($Viewer->option('DownloadAlt') === '1'); header('Content-Type: ' . ($downloadAsText ? 'text/plain' : 'application/x-bittorrent') . '; charset=utf-8'); -header('Content-Disposition: attachment; filename="' . $torMan->torrentFilename($info, $downloadAsText) . '"'); -echo $torMan->torrentBody($torrentId, $Viewer->announceUrl()); +header('Content-Disposition: attachment; filename="' . $torrent->torrentFilename($downloadAsText, MAX_PATH_LEN) . '"'); +echo $torrent->torrentBody($Viewer->announceUrl()); diff --git a/sections/torrents/history.php b/sections/torrents/history.php index 78ee11d2f..dcca13704 100644 --- a/sections/torrents/history.php +++ b/sections/torrents/history.php @@ -1,7 +1,6 @@ findGroupById((int)$_GET['groupid']); +$group = (new Gazelle\Manager\TGroup)->findById((int)($_GET['groupid'] ?? 0)); if (is_null($group)) { error(404); } diff --git a/sections/torrents/peerlist.php b/sections/torrents/peerlist.php index a9963c15f..3035f23e4 100644 --- a/sections/torrents/peerlist.php +++ b/sections/torrents/peerlist.php @@ -1,17 +1,17 @@ findById((int)$_GET['torrentid']); +if (is_null($torrent)) { error(404); } -$torMan = new Gazelle\Manager\Torrent; $paginator = new Gazelle\Util\Paginator(PEERS_PER_PAGE, (int)($_GET['page'] ?? 1)); -$paginator->setTotal($torMan->peerlistTotal($torrentId)); +$paginator->setTotal($torrent->peerlistTotal()); echo $Twig->render('torrent/peerlist.twig', [ 'is_admin' => check_perms('users_mod'), - 'linkbox' => $paginator->linkboxJS('show_peers', $torrentId), - 'list' => $torMan->peerlistPage($torrentId, $LoggedUser['ID'], $paginator->limit(), $paginator->offset()), - 'torrent_id' => $torrentId, + 'linkbox' => $paginator->linkboxJS('show_peers', $torrent->id()), + 'list' => $torrent->peerlistPage($LoggedUser['ID'], $paginator->limit(), $paginator->offset()), + 'torrent_id' => $torrent->id(), 'user_id' => $LoggedUser['ID'], ]); diff --git a/sections/torrents/remove_logs.php b/sections/torrents/remove_logs.php index c7c81aea1..f1acaa58a 100644 --- a/sections/torrents/remove_logs.php +++ b/sections/torrents/remove_logs.php @@ -18,5 +18,5 @@ $GroupID = $DB->scalar('SELECT GroupID FROM torrents WHERE ID = ?', $TorrentID); (new Gazelle\Log)->torrent($GroupID, $TorrentID, $LoggedUser['ID'], "All logs removed from torrent"); -$Cache->deleteMulti(["torrent_group_$GroupID", "torrents_details_$GroupID"]); +$Cache->deleteMulti(["torrent_group_$GroupID", "torrents_details_$GroupID", "tg_$GroupID", "tlist_$GroupID"]); header('Location: ' . $_SERVER['HTTP_REFERER'] ?? "torrents.php?torrentid={$TorrentID}"); diff --git a/sections/torrents/rescore_log.php b/sections/torrents/rescore_log.php index 2d00808a2..689a63885 100644 --- a/sections/torrents/rescore_log.php +++ b/sections/torrents/rescore_log.php @@ -6,24 +6,16 @@ error(403); } -$torrentId = (int)$_GET['torrentid']; -$logId = (int)$_GET['logid']; -if (!$DB->scalar(" - SELECT 1 FROM torrents_logs WHERE LogID = ? AND TorrentID = ? - ", $logId, $torrentId) -) { - error(404); -} -$groupId = $DB->scalar('SELECT GroupID FROM torrents WHERE ID = ?', $torrentId); -if (!$groupId) { +$torrent = (new Gazelle\Manager\Torrent)->findById((int)$_GET['torrentid']); +$logId = (int)$_GET['logid']; +if (is_null($torrent) || !$logId) { error(404); } -$logpath = (new Gazelle\File\RipLog)->path([$torrentId, $logId]); +$logpath = (new Gazelle\File\RipLog)->path([$torrent->id(), $logId]); $logfile = new Gazelle\Logfile($logpath, basename($logpath)); -(new Gazelle\File\RipLogHTML)->put($logfile->text(), [$torrentId, $logId]); +(new Gazelle\File\RipLogHTML)->put($logfile->text(), [$torrent->id(), $logId]); -$torMan = new Gazelle\Manager\Torrent; -$torMan->rescoreLog($groupId, $torrentId, $logId, $logfile, Logchecker::getLogcheckerVersion()); +$torrent->rescoreLog($logId, $logfile, Logchecker::getLogcheckerVersion()); -header("Location: torrents.php?torrentid={$torrentId}"); +header("Location: torrents.php?torrentid=" . $torrent->id()); diff --git a/sections/torrents/take_edit_log.php b/sections/torrents/take_edit_log.php index 27e7414cb..5637d0838 100644 --- a/sections/torrents/take_edit_log.php +++ b/sections/torrents/take_edit_log.php @@ -4,28 +4,22 @@ error(403); } -$TorrentID = intval($_POST['torrentid']); -$LogID = intval($_POST['logid']); - -[$GroupID, $Checksum] = $DB->row(" - SELECT t.GroupID, tl.Checksum - FROM torrents_logs tl - INNER JOIN torrents t ON (tl.TorrentID = t.ID) - WHERE tl.TorrentID = ? AND tl.LogID = ? - ", $TorrentID, $LogID -); -if (!$GroupID) { +$LogID = (int)($_POST['logid'] ?? 0); +if (!$LogID) { + error(404); +} +$torrent = (new Gazelle\Manager\Torrent)->findById((int)($_POST['torrentid'] ?? 0)); +if (is_null($torrent)) { error(404); } $Adjusted = isset($_POST['adjusted']) ? '1' : '0'; $AdjustedScore = 100; $AdjustedChecksum = isset($_POST['adjusted_checksum']) ? '1' : '0'; -$AdjustedBy = $LoggedUser['ID']; $AdjustmentReason = $_POST['adjustment_reason']; $AdjustmentDetails = []; -if ($AdjustedChecksum != $Checksum) { +if ($AdjustedChecksum != $torrent->info()['Checksum']) { $AdjustmentDetails['checksum'] = 'Checksum manually '.($AdjustedChecksum == '1' ? 'validated' : 'invalidated'); } @@ -73,6 +67,6 @@ $AdjustedScore -= $Total; } -(new Gazelle\Manager\Torrent)->adjustLogscore($GroupID, $TorrentID, $LogID, $Adjusted, max(0, $AdjustedScore), $AdjustedChecksum, $AdjustedBy, $AdjustmentReason, serialize($AdjustmentDetails)); +$torrent->adjustLogscore($LogID, $Adjusted, max(0, $AdjustedScore), $AdjustedChecksum, $LoggedUser['ID'], $AdjustmentReason, $AdjustmentDetails); -header("Location: torrents.php?torrentid={$TorrentID}"); +header("Location: torrents.php?torrentid=" . $torrent->id()); diff --git a/sections/torrents/takedelete.php b/sections/torrents/takedelete.php index 55294731c..1479efee5 100644 --- a/sections/torrents/takedelete.php +++ b/sections/torrents/takedelete.php @@ -1,21 +1,14 @@ setTorrentId($torrentId) - ->setViewer($LoggedUser['ID']) - ->setArtistDisplayText(); - -[$group, $torrent] = $torMan->torrentInfo(); - -if (!$torrent) { +$t = (new Gazelle\Manager\Torrent)->findById((int)$_POST['torrentid']); +if (is_null($t)) { error(404); } +$t->setViewerId($LoggedUser['ID']); +$tgroup = $t->group(); +$group = $tgroup->info(); +$torrent = $t->info(); if ($LoggedUser['ID'] != $torrent['UserID'] && !check_perms('torrents_delete')) { error(403); @@ -36,16 +29,14 @@ ->showEdition(true) ->load($torrent); -$name = $group['Name'] - . " [" . $labelMan->release() - . '] (' . $labelMan->edition() . ')'; -$artistName = $torMan->setGroupID($group['ID'])->artistName(); +$name = $group['Name'] . " [" . $labelMan->release() . '] (' . $labelMan->edition() . ')'; +$artistName = $tgroup()->artistName(); if ($artistName) { $name = "$artistName - $name"; } $reason = trim($_POST['reason']) . ' ' . trim($_POST['extra']); -[$success, $message] = $torMan->remove($reason); +[$success, $message] = $torrent->remove($reason); if (!$success) { error($message); } diff --git a/sections/upload/upload_handle.php b/sections/upload/upload_handle.php index 98d53e78c..1e7f6de5f 100644 --- a/sections/upload/upload_handle.php +++ b/sections/upload/upload_handle.php @@ -715,7 +715,7 @@ $Cache->cache_value("torrent_{$TorrentID}_lock", true, 300); if (in_array($Properties['Encoding'], ['Lossless', '24bit Lossless'])) { - $torMan->flushLatestUploads(5); + (new Gazelle\Manager\TGroup)->flushLatestUploads(5); } //******************************************************************************// @@ -961,7 +961,7 @@ ->addEncodings($Properties['Encoding']) ->addMedia($Properties['Media']) ->addYear($Properties['Year'], $Properties['RemasterYear']) - ->addArtists($torMan->setGroupId($GroupID)->artistRole()) + ->addArtists((new Gazelle\Manager\TGroup)->findById($GroupID)->artistRole()) ->addTags($tagList) ->addCategory($Type) ->addReleaseType($releaseTypes[$Properties['ReleaseType']]) @@ -990,5 +990,5 @@ // Clear cache and allow deletion of this torrent now $Cache->deleteMulti(["torrents_details_$GroupID", "torrent_{$TorrentID}_lock"]); if (!$IsNewGroup) { - $Cache->deleteMulti(["torrent_group_$GroupID", "detail_files_$GroupID"]); + $Cache->deleteMulti(["torrent_group_$GroupID", "detail_files_$GroupID", "tg_$GroupID", "tlist_$GroupID"]); } diff --git a/sections/user/2fa/configure.php b/sections/user/2fa/configure.php index 52cfe01e2..2478e12dd 100644 --- a/sections/user/2fa/configure.php +++ b/sections/user/2fa/configure.php @@ -12,7 +12,6 @@ if (!empty($_SESSION['private_key'])) { $secret = $_SESSION['private_key']; if (isset($_POST['2fa'])) { - var_dump([$secret, $_POST]); if ($auth->verifyCode($secret, trim($_POST['2fa']), 2)) { header('Location: user.php?action=2fa&do=complete&userid=' . $userId); exit; diff --git a/sections/userhistory/token_history.php b/sections/userhistory/token_history.php index 5dfbd6c8b..9d140e042 100644 --- a/sections/userhistory/token_history.php +++ b/sections/userhistory/token_history.php @@ -22,11 +22,11 @@ if (!check_perms('admin_fl_history')) { error(403); } - $torrentId = (int)$_GET['torrentid']; - if (!$torrentId) { + $torrent = $torMan->findById((int)$_GET['torrentid']); + if (is_null($torrent)) { error(404); } - $torMan->expireToken($userId, $torrentId); + $torrent->expireToken($userId); header("Location: userhistory.php?action=token_history&userid=$userId"); }