string] * @access protected */ protected $_tableNames; /** * Default constructor. * * @param \newznab\db\Settings $pdo * * @access public */ public function __construct(&$pdo = null) { $this->pdo = ($pdo instanceof Settings ? $pdo : new Settings()); $this->tablePerGroup = ($this->pdo->getSetting('tablepergroup') == 0 ? false : true); $nzbSplitLevel = $this->pdo->getSetting('nzbsplitlevel'); $this->nzbSplitLevel = (empty($nzbSplitLevel) ? 1 : $nzbSplitLevel); $this->siteNzbPath = (string)$this->pdo->getSetting('nzbpath'); if (substr($this->siteNzbPath, -1) !== DS) { $this->siteNzbPath .= DS; } $this->_debug = (NN_DEBUG || NN_LOGGING); if (NN_DEBUG || NN_LOGGING) { $this->_debug = true; try { $this->debugging = new Logger(['ColorCLI' => $this->pdo->log]); } catch (LoggerException $error) { $this->_debug = false; } } } /** * Initiate class vars when writing NZB's. * * @param int $groupID * * @access public */ public function initiateForWrite($groupID) { $this->groupID = $groupID; // Set table names if ($this->tablePerGroup === true) { if ($this->groupID == '') { exit("$this->groupID is missing\n"); } $this->_tableNames = [ 'cName' => 'collections_' . $this->groupID, 'bName' => 'binaries_' . $this->groupID, 'pName' => 'parts_' . $this->groupID ]; } else { $this->_tableNames = [ 'cName' => 'collections', 'bName' => 'binaries', 'pName' => 'parts' ]; } $this->_collectionsQuery = sprintf( 'SELECT %s.*, UNIX_TIMESTAMP(%s.date) AS udate, groups.name AS groupname FROM %s INNER JOIN groups ON %s.group_id = groups.id WHERE %s.releaseid = ', $this->_tableNames['cName'], $this->_tableNames['cName'], $this->_tableNames['cName'], $this->_tableNames['cName'], $this->_tableNames['cName'] ); $this->_binariesQuery = ( 'SELECT id, name, totalparts FROM ' . $this->_tableNames['bName'] . ' WHERE collection_id = %d ORDER BY name' ); $this->_partsQuery = ( 'SELECT DISTINCT(messageid), size, partnumber FROM ' . $this->_tableNames['pName'] . ' WHERE binaryid = %d ORDER BY partnumber' ); $this->_nzbHeadString = ( "\n\n\n\n\n %s\n %s\n\n\n" ); } /** * Write an NZB to the hard drive for a single release. * * @param int $relID The ID of the release in the DB. * @param string $relGuid The guid of the release. * @param string $name The name of the release. * @param string $cTitle The name of the category this release is in. * * @return bool Have we successfully written the NZB to the hard drive? * * @access public */ public function writeNZBforReleaseId($relID, $relGuid, $name, $cTitle) { $path = ($this->buildNZBPath($relGuid, $this->nzbSplitLevel, true) . $relGuid . '.nzb.gz'); $fp = gzopen($path, 'wb7'); $complete = false; if ($fp) { $nzb_guid = ''; gzwrite( $fp, sprintf( $this->_nzbHeadString, htmlspecialchars($cTitle, ENT_QUOTES, 'utf-8'), htmlspecialchars($name, ENT_QUOTES, 'utf-8') ) ); $collections = $this->pdo->queryDirect($this->_collectionsQuery . $relID); if ($collections instanceof \Traversable) { foreach ($collections as $collection) { $poster = htmlspecialchars($collection['fromname'], ENT_QUOTES, 'utf-8'); $binaries = $this->pdo->queryDirect(sprintf($this->_binariesQuery, $collection['id'])); if ($binaries instanceof \Traversable) { foreach ($binaries as $binary) { // Buffer segment writes, increases performance. $string = ''; $parts = $this->pdo->queryDirect(sprintf($this->_partsQuery, $binary['id'])); if ($parts instanceof \Traversable) { foreach ($parts as $part) { if ($nzb_guid === '') { $nzb_guid = $part['messageid']; } $string .= ( ' ' . htmlspecialchars($part['messageid'], ENT_QUOTES, 'utf-8') . "\n" ); } $complete = true; } else { if ($this->_debug) { $this->debugging->log( get_class(), __FUNCTION__, 'ERROR: no parts for release ' . $relID, Logger::LOG_WARNING ); } } gzwrite($fp, '\n \n " . $collection['groupname'] . "\n \n \n" . $string . " \n\n" ); } } else { if ($this->_debug) { $this->debugging->log( get_class(), __FUNCTION__, 'ERROR: no binaries for release ' . $relID, Logger::LOG_WARNING ); } } } } else { if ($this->_debug) { $this->debugging->log( get_class(), __FUNCTION__, 'ERROR: no collections for release ' . $relID, Logger::LOG_WARNING ); } } gzwrite($fp, ''); gzclose($fp); if ($complete) { if (is_file($path)) { // Mark release as having NZB. $this->pdo->queryExec( sprintf(' UPDATE releases SET nzbstatus = %d %s WHERE id = %d', NZB::NZB_ADDED, ($nzb_guid === '' ? '' : ', nzb_guid = UNHEX( ' . $this->pdo->escapeString(md5($nzb_guid)) . ' )'), $relID ) ); // Delete CBP for release that has its NZB created. $this->pdo->queryExec( sprintf(' DELETE c, b, p FROM %s c JOIN %s b ON(c.id=b.collection_id) STRAIGHT_JOIN %s p ON(b.id=p.binaryid) WHERE c.releaseid = %d', $this->_tableNames['cName'], $this->_tableNames['bName'], $this->_tableNames['pName'], $relID ) ); // Chmod to fix issues some users have with file permissions. chmod($path, 0777); return true; } else { echo "ERROR: $path does not exist.\n"; } } else { if (is_file($path)) { @unlink($path); } } } return false; } /** * Build a folder path on the hard drive where the NZB file will be stored. * * @param string $releaseGuid The guid of the release. * @param int $levelsToSplit How many sub-paths the folder will be in. * @param bool $createIfNotExist Create the folder if it doesn't exist. * * @return string $nzbpath The path to store the NZB file. * * @access public */ private function buildNZBPath($releaseGuid, $levelsToSplit, $createIfNotExist) { $nzbPath = ''; for ($i = 0; $i < $levelsToSplit && $i < 32; $i++) { $nzbPath .= substr($releaseGuid, $i, 1) . DS; } $nzbPath = $this->siteNzbPath . $nzbPath; if ($createIfNotExist === true && !is_dir($nzbPath)) { mkdir($nzbPath, 0777, true); } return $nzbPath; } /** * Retrieve path + filename of the NZB to be stored. * * @param string $releaseGuid The guid of the release. * @param int $levelsToSplit How many sub-paths the folder will be in. (optional) * @param bool $createIfNotExist Create the folder if it doesn't exist. (optional) * * @return string Path+filename. * * @access public */ public function getNZBPath($releaseGuid, $levelsToSplit = 0, $createIfNotExist = false) { if ($levelsToSplit === 0) { $levelsToSplit = $this->nzbSplitLevel; } return ($this->buildNZBPath($releaseGuid, $levelsToSplit, $createIfNotExist) . $releaseGuid . '.nzb.gz'); } /** * Determine is an NZB exists, returning the path+filename, if not return false. * * @param string $releaseGuid The guid of the release. * * @return bool|string On success: (string) Path+file name of the nzb. * On failure: (bool) False. * * @access public */ public function NZBPath($releaseGuid) { $nzbFile = $this->getNZBPath($releaseGuid); return (is_file($nzbFile) ? $nzbFile : false); } /** * Retrieve various information on a NZB file (the subject, # of pars, * file extensions, file sizes, file completion, group names, # of parts). * * @param string $nzb The NZB contents in a string. * @param array $options * 'no-file-key' => True - use numeric array key; False - Use filename as array key. * 'strip-count' => True - Strip file/part count from file name to make the array key; False - Leave file name as is. * * @return array $result Empty if not an NZB or the contents of the NZB. * * @access public */ public function nzbFileList($nzb, array $options = []) { $defaults = [ 'no-file-key' => true, 'strip-count' => false, ]; $options += $defaults; $num_pars = $i = 0; $result = []; if (!$nzb) { return $result; } $xml = @simplexml_load_string(str_replace("\x0F", '', $nzb)); if (!$xml || strtolower($xml->getName()) !== 'nzb') { return $result; } foreach ($xml->file as $file) { // Subject. $title = (string)$file->attributes()->subject; // Amount of pars. if (stripos($title, '.par2')) { $num_pars++; } if ($options['no-file-key'] == false) { $i = $title; if ($options['strip-count']) { // Strip file / part count to get proper sorting. $i = preg_replace('#\d+[- ._]?(/|\||[o0]f)[- ._]?\d+?(?![- ._]\d)#i', '', $i); // Change .rar and .par2 to be sorted before .part0x.rar and .volxxx+xxx.par2 if (strpos($i, '.par2') !== false && !preg_match('#\.vol\d+\+\d+\.par2#i', $i)) { $i = str_replace('.par2', '.vol0.par2', $i); } else if (preg_match('#\.rar[^a-z0-9]#i', $i) && !preg_match('#\.part\d+\.rar#i', $i)) { $i = preg_replace('#\.rar(?:[^a-z0-9])#i', '.part0.rar', $i); } } } $result[$i]['title'] = $title; // Extensions. if (preg_match( '/\.(\d{2,3}|7z|ace|ai7|srr|srt|sub|aiff|asc|avi|audio|bin|bz2|' . 'c|cfc|cfm|chm|class|conf|cpp|cs|css|csv|cue|deb|divx|doc|dot|' . 'eml|enc|exe|file|gif|gz|hlp|htm|html|image|iso|jar|java|jpeg|' . 'jpg|js|lua|m|m3u|mkv|mm|mov|mp3|mp4|mpg|nfo|nzb|odc|odf|odg|odi|odp|' . 'ods|odt|ogg|par2|parity|pdf|pgp|php|pl|png|ppt|ps|py|r\d{2,3}|' . 'ram|rar|rb|rm|rpm|rtf|sfv|sig|sql|srs|swf|sxc|sxd|sxi|sxw|tar|' . 'tex|tgz|txt|vcf|video|vsd|wav|wma|wmv|xls|xml|xpi|xvid|zip7|zip)' . '[" ](?!(\)|\-))/i', $title, $ext ) ) { if (preg_match('/\.r\d{2,3}/i', $ext[0])) { $ext[1] = 'rar'; } $result[$i]['ext'] = strtolower($ext[1]); } else { $result[$i]['ext'] = ''; } $fileSize = $numSegments = 0; // Parts. if (!isset($result[$i]['segments'])) { $result[$i]['segments'] = []; } // File size. foreach ($file->segments->segment as $segment) { $result[$i]['segments'][] = (string)$segment; $fileSize += $segment->attributes()->bytes; $numSegments++; } $result[$i]['size'] = $fileSize; // File completion. if (preg_match('/(\d+)\)$/', $title, $parts)) { $result[$i]['partstotal'] = $parts[1]; } $result[$i]['partsactual'] = $numSegments; // Groups. if (!isset($result[$i]['groups'])) { $result[$i]['groups'] = []; } foreach ($file->groups->group as $g) { $result[$i]['groups'][] = (string)$g; } unset($result[$i]['segments']['@attributes']); if ($options['no-file-key']) { $i++; } } return $result; } }