pdo = DB::connection()->getPdo(); $this->colorCli = new ColorCLI; } /** * @return mixed */ public function getConnectionsInfo($constants) { $runVar['connections']['port_a'] = $runVar['connections']['host_a'] = $runVar['connections']['ip_a'] = false; $runVar['connections']['port'] = config('nntmux_nntp.port'); $runVar['connections']['host'] = config('nntmux_nntp.server'); $runVar['connections']['ip'] = gethostbyname($runVar['connections']['host']); if ($constants['alternate_nntp'] === '1') { $runVar['connections']['port_a'] = config('nntmux_nntp.alternate_server_port'); $runVar['connections']['host_a'] = config('nntmux_nntp.alternate_server'); $runVar['connections']['ip_a'] = gethostbyname($runVar['connections']['host_a']); } return $runVar['connections']; } public function getUSPConnections(string $which, $connections): mixed { switch ($which) { case 'alternate': $ip = 'ip_a'; $port = 'port_a'; break; case 'primary': default: $ip = 'ip'; $port = 'port'; break; } // Initialize result structure $runVar['conncounts'][$which]['active'] = '0'; $runVar['conncounts'][$which]['total'] = '0'; // 1) Try exact host:port if (! empty($connections[$ip]) && ! empty($connections[$port])) { $needle = escapeshellarg($connections[$ip].':'.$connections[$port]); $runVar['conncounts'][$which]['active'] = str_replace("\n", '', shell_exec("ss -n | grep -- $needle | grep -c -- ESTAB")) ?: '0'; $runVar['conncounts'][$which]['total'] = str_replace("\n", '', shell_exec("ss -n | grep -c -- $needle")) ?: '0'; } // 2) Fallback to host:https if ((int) $runVar['conncounts'][$which]['active'] === 0 && (int) $runVar['conncounts'][$which]['total'] === 0 && ! empty($connections[$ip])) { $needleHttps = escapeshellarg($connections[$ip].':https'); $runVar['conncounts'][$which]['active'] = str_replace("\n", '', shell_exec("ss -n | grep -- $needleHttps | grep -c -- ESTAB")) ?: '0'; $runVar['conncounts'][$which]['total'] = str_replace("\n", '', shell_exec("ss -n | grep -c -- $needleHttps")) ?: '0'; } // 3) Fallback to port only if ((int) $runVar['conncounts'][$which]['active'] === 0 && (int) $runVar['conncounts'][$which]['total'] === 0 && ! empty($connections[$port])) { $needlePort = escapeshellarg((string) $connections[$port]); $runVar['conncounts'][$which]['active'] = str_replace("\n", '', shell_exec("ss -n | grep -- $needlePort | grep -c -- ESTAB")) ?: '0'; $runVar['conncounts'][$which]['total'] = str_replace("\n", '', shell_exec("ss -n | grep -c -- $needlePort")) ?: '0'; } // 4) Fallback to host only if ((int) $runVar['conncounts'][$which]['active'] === 0 && (int) $runVar['conncounts'][$which]['total'] === 0 && ! empty($connections[$ip])) { $needleIp = escapeshellarg($connections[$ip]); $runVar['conncounts'][$which]['active'] = str_replace("\n", '', shell_exec("ss -n | grep -- $needleIp | grep -c -- ESTAB")) ?: '0'; $runVar['conncounts'][$which]['total'] = str_replace("\n", '', shell_exec("ss -n | grep -c -- $needleIp")) ?: '0'; } return $runVar['conncounts']; } public function getListOfPanes($constants): array { $panes = ['zero' => '', 'one' => '', 'two' => '']; switch ($constants['sequential']) { case 0: case 1: $panes_win_1 = shell_exec("echo `tmux list-panes -t {$constants['tmux_session']}:0 -F '#{pane_title}'`"); $panes['zero'] = str_replace("\n", '', explode(' ', $panes_win_1)); $panes_win_2 = shell_exec("echo `tmux list-panes -t {$constants['tmux_session']}:1 -F '#{pane_title}'`"); $panes['one'] = str_replace("\n", '', explode(' ', $panes_win_2)); $panes_win_3 = shell_exec("echo `tmux list-panes -t {$constants['tmux_session']}:2 -F '#{pane_title}'`"); $panes['two'] = str_replace("\n", '', explode(' ', $panes_win_3)); break; case 2: $panes_win_1 = shell_exec("echo `tmux list-panes -t {$constants['tmux_session']}:0 -F '#{pane_title}'`"); $panes['zero'] = str_replace("\n", '', explode(' ', $panes_win_1)); $panes_win_2 = shell_exec("echo `tmux list-panes -t {$constants['tmux_session']}:1 -F '#{pane_title}'`"); $panes['one'] = str_replace("\n", '', explode(' ', $panes_win_2)); break; } return $panes; } public function getConstantSettings(): array { $settings = [ 'sequential', 'tmux_session', 'run_ircscraper', 'delaytime', ]; $constants = Settings::query() ->whereIn('name', $settings) ->pluck('value', 'name') ->toArray(); $constants['alternate_nntp'] = config('nntmux_nntp.use_alternate_nntp_server') ? '1' : '0'; return $constants; } public function getMonitorSettings(): array { $settingsMap = [ 'monitor_delay' => 'monitor', 'binaries' => 'binaries_run', 'backfill' => 'backfill', 'backfill_qty' => 'backfill_qty', 'nzbs' => 'nzbs', 'post' => 'post', 'releases' => 'releases_run', 'fix_names' => 'fix_names', 'seq_timer' => 'seq_timer', 'bins_timer' => 'bins_timer', 'back_timer' => 'back_timer', 'rel_timer' => 'rel_timer', 'fix_timer' => 'fix_timer', 'post_timer' => 'post_timer', 'collections_kill' => 'collections_kill', 'postprocess_kill' => 'postprocess_kill', 'crap_timer' => 'crap_timer', 'fix_crap' => 'fix_crap', 'fix_crap_opt' => 'fix_crap_opt', 'post_kill_timer' => 'post_kill_timer', 'monitor_path' => 'monitor_path', 'monitor_path_a' => 'monitor_path_a', 'monitor_path_b' => 'monitor_path_b', 'progressive' => 'progressive', 'dehash' => 'dehash', 'dehash_timer' => 'dehash_timer', 'backfill_days' => 'backfilldays', 'post_amazon' => 'post_amazon', 'post_timer_amazon' => 'post_timer_amazon', 'post_non' => 'post_non', 'post_timer_non' => 'post_timer_non', 'colors_start' => 'colors_start', 'colors_end' => 'colors_end', 'colors_exc' => 'colors_exc', 'showquery' => 'show_query', 'running' => 'is_running', 'lookupbooks' => 'processbooks', 'lookupmusic' => 'processmusic', 'lookupgames' => 'processgames', 'lookupxxx' => 'processxxx', 'lookupimdb' => 'processmovies', 'lookuptv' => 'processtvrage', 'lookupanidb' => 'processanime', 'lookupnfo' => 'processnfo', 'lookuppar2' => 'processpar2', 'nzbthreads' => 'nzbthreads', 'maxsizetopostprocess' => 'maxsize_pp', 'minsizetopostprocess' => 'minsize_pp', ]; return Settings::query() ->whereIn('name', array_keys($settingsMap)) ->get() ->mapWithKeys(function ($item) use ($settingsMap) { return [$settingsMap[$item->name] => $item->value]; }) ->toArray(); } public function updateItem($setting, $value): int { return Settings::query()->where('name', '=', $setting)->update(['value' => $value]); } public function microtime_float(): float { [$usec, $sec] = explode(' ', microtime()); return (float) $usec + (float) $sec; } public function decodeSize(int|float $bytes): string { // Handle zero case if ($bytes === 0) { return '0 B'; } // Handle negative values if ($bytes < 0) { return '-'.$this->decodeSize(abs($bytes)); } // Add more size units (PB, EB) $types = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB']; $index = 0; // Use while loop instead of foreach for more efficiency while ($bytes >= 1024.0 && $index < count($types) - 1) { $bytes /= 1024.0; $index++; } return round($bytes, 2).' '.$types[$index]; } public function writelog($pane): ?string { $path = storage_path('logs'); $getDate = now()->format('Y_m_d'); $logs = Settings::settingValue('write_logs') ?? 0; if ($logs === 1) { return "2>&1 | tee -a $path/$pane-$getDate.log"; } return ''; } /** * @throws \Exception */ public function get_color($colors_start, $colors_end, $colors_exc): int { $exception = str_replace('.', '.', $colors_exc); $exceptions = explode(',', $exception); sort($exceptions); $number = random_int($colors_start, $colors_end - \count($exceptions)); foreach ($exceptions as $exception) { if ($number >= $exception) { $number++; } else { break; } } return $number; } public function relativeTime($_time): string { return Carbon::createFromTimestamp($_time, date_default_timezone_get())->ago(); } /** * @throws \Exception */ public function proc_query($qry, $bookreqids, string $db_name, string $ppmax = '', string $ppmin = ''): bool|string { switch ((int) $qry) { case 1: return sprintf( ' SELECT SUM(IF(categories_id BETWEEN %d AND %d AND categories_id != %d AND videos_id = 0 AND tv_episodes_id BETWEEN -3 AND 0 AND size > 1048576,1,0)) AS processtv, SUM(IF(categories_id = %d AND anidbid IS NULL,1,0)) AS processanime, SUM(IF(categories_id BETWEEN %d AND %d AND imdbid IS NULL,1,0)) AS processmovies, SUM(IF(categories_id IN (%d, %d, %d) AND musicinfo_id IS NULL,1,0)) AS processmusic, SUM(IF(categories_id BETWEEN %d AND %d AND consoleinfo_id IS NULL,1,0)) AS processconsole, SUM(IF(categories_id IN (%s) AND bookinfo_id IS NULL,1,0)) AS processbooks, SUM(IF(categories_id = %d AND gamesinfo_id = 0,1,0)) AS processgames, SUM(IF(categories_id BETWEEN %d AND %d AND xxxinfo_id = 0,1,0)) AS processxxx, SUM(IF(1=1 %s,1,0)) AS processnfo, SUM(IF(isrenamed = %d AND predb_id = 0 AND passwordstatus >= 0 AND nfostatus > %d AND ((nfostatus = %d AND proc_nfo = %d) OR proc_files = %d OR proc_par2 = %d OR (ishashed = 1 AND dehashstatus BETWEEN -6 AND 0)) AND categories_id IN (%s),1,0)) AS processrenames, SUM(IF(isrenamed = %d,1,0)) AS renamed, SUM(IF(nfostatus = %20$d,1,0)) AS nfo, SUM(IF(predb_id > 0,1,0)) AS predb_matched, COUNT(DISTINCT(predb_id)) AS distinct_predb_matched FROM releases r', Category::TV_ROOT, Category::TV_OTHER, Category::TV_ANIME, Category::TV_ANIME, Category::MOVIE_ROOT, Category::MOVIE_OTHER, Category::MUSIC_MP3, Category::MUSIC_LOSSLESS, Category::MUSIC_OTHER, Category::GAME_ROOT, Category::GAME_OTHER, $bookreqids, Category::PC_GAMES, Category::XXX_ROOT, Category::XXX_X264, Nfo::NfoQueryString(), NameFixer::IS_RENAMED_NONE, Nfo::NFO_UNPROC, Nfo::NFO_FOUND, NameFixer::PROC_NFO_NONE, NameFixer::PROC_FILES_NONE, NameFixer::PROC_PAR2_NONE, Category::getCategoryOthersGroup(), NameFixer::IS_RENAMED_DONE ); case 2: $ppminString = $ppmaxString = ''; if (is_numeric($ppmax) && ! empty($ppmax)) { $ppmax *= 1073741824; $ppmaxString = "AND r.size < {$ppmax}"; } if (is_numeric($ppmin) && ! empty($ppmin)) { $ppmin *= 1048576; $ppminString = "AND r.size > {$ppmin}"; } return "SELECT (SELECT COUNT(r.id) FROM releases r LEFT JOIN categories c ON c.id = r.categories_id WHERE r.passwordstatus = -1 AND r.haspreview = -1 {$ppminString} {$ppmaxString} AND c.disablepreview = 0 ) AS work, (SELECT COUNT(id) FROM usenet_groups WHERE active = 1) AS active_groups, (SELECT COUNT(id) FROM usenet_groups WHERE name IS NOT NULL) AS all_groups"; case 4: return sprintf( " SELECT (SELECT TABLE_ROWS FROM information_schema.TABLES WHERE table_name = 'predb' AND TABLE_SCHEMA = %1\$s) AS predb, (SELECT TABLE_ROWS FROM information_schema.TABLES WHERE table_name = 'missed_parts' AND TABLE_SCHEMA = %1\$s) AS missed_parts_table, (SELECT TABLE_ROWS FROM information_schema.TABLES WHERE table_name = 'parts' AND TABLE_SCHEMA = %1\$s) AS parts_table, (SELECT TABLE_ROWS FROM information_schema.TABLES WHERE table_name = 'binaries' AND TABLE_SCHEMA = %1\$s) AS binaries_table, (SELECT TABLE_ROWS FROM information_schema.TABLES WHERE table_name = 'collections' AND TABLE_SCHEMA = %1\$s) AS releases, (SELECT COUNT(id) FROM usenet_groups WHERE first_record IS NOT NULL AND backfill = 1 AND (now() - INTERVAL backfill_target DAY) < first_record_postdate ) AS backfill_groups_days, (SELECT COUNT(id) FROM usenet_groups WHERE first_record IS NOT NULL AND backfill = 1 AND (now() - INTERVAL datediff(curdate(), (SELECT VALUE FROM settings WHERE name = 'safebackfilldate')) DAY) < first_record_postdate) AS backfill_groups_date", escapeString($db_name) ); case 6: return 'SELECT (SELECT searchname FROM releases ORDER BY id DESC LIMIT 1) AS newestrelname, (SELECT UNIX_TIMESTAMP(MIN(dateadded)) FROM collections) AS oldestcollection, (SELECT UNIX_TIMESTAMP(MAX(predate)) FROM predb) AS newestpre, (SELECT UNIX_TIMESTAMP(adddate) FROM releases ORDER BY id DESC LIMIT 1) AS newestrelease'; default: return false; } } /** * @return bool true if tmux is running, false otherwise. * * @throws \RuntimeException */ public function isRunning(): bool { $running = Settings::query()->where(['name' => 'running'])->first(['value']); if ($running === null) { throw new \RuntimeException('Tmux\\\'s running flag was not found in the database.'.PHP_EOL.'Please check the tables are correctly setup.'.PHP_EOL); } return ! ((int) $running->value === 0); } /** * @throws \Exception */ public function stopIfRunning(): bool { if ($this->isRunning()) { Settings::query()->where(['name' => 'running'])->update(['value' => 0]); $sleep = Settings::settingValue('monitor_delay'); $this->colorCli->header('Stopping tmux scripts and waiting '.$sleep.' seconds for all panes to shutdown'); sleep($sleep); return true; } $this->colorCli->info('Tmux scripts are not running!'); return false; } /** * @throws \RuntimeException */ public function startRunning(): void { if (! $this->isRunning()) { Settings::query()->where(['name' => 'running'])->update(['value' => 1]); } } public function cbpmTableQuery(): array { return DB::select( " SELECT TABLE_NAME AS name FROM information_schema.TABLES WHERE TABLE_SCHEMA = (SELECT DATABASE()) AND TABLE_NAME REGEXP {escapeString('^(multigroup_)?(collections|binaries|parts|missed_parts)(_[0-9]+)?$')} ORDER BY TABLE_NAME ASC" ); } }