diff --git a/bin/monitor.php b/bin/monitor.php index 59671187c..00edf9fad 100644 --- a/bin/monitor.php +++ b/bin/monitor.php @@ -8,7 +8,7 @@ require_once(WWW_DIR . "/lib/showsleep.php"); require_once(dirname(__FILE__) . "/../lib/functions.php"); -$version = "0.4r2300"; +$version = "0.4r2301"; $pdo = new DB(); $s = new Sites(); @@ -149,7 +149,7 @@ $split_query = "SELECT $split_query = "SELECT " . "(SELECT COUNT(*) FROM groups WHERE first_record IS NOT NULL AND (now() - interval backfill_target day) < first_record_postdate) AS backfill_groups_days, " . "(SELECT COUNT(*) FROM groups WHERE first_record IS NOT NULL AND (now() - interval datediff(curdate(), " - . "(SELECT VALUE FROM tmux WHERE SETTING = 'safebackfilldate')) day) < first_record_postdate) AS backfill_groups_date, " + . "(SELECT VALUE FROM site WHERE SETTING = 'safebackfilldate')) day) < first_record_postdate) AS backfill_groups_date, " . "(SELECT UNIX_TIMESTAMP(adddate) FROM releases USE INDEX(ix_releases_status) ORDER BY adddate DESC LIMIT 1 ) AS newestadd"; //get first release inserted datetime and oldest posted datetime @@ -1314,7 +1314,7 @@ while ($i > 0) { if (($backfill == 4) && ($kill_pp == "false") && (TIME() - $time6 <= 4800)) { $log = writelog($panes0[3]); shell_exec("tmux respawnp -t${tmux_session}:0.3 ' \ - $_python ${DIR}/../python/backfill_safe_threaded.py $log; date +\"%D %T\"; $_sleep $backsleep' 2>&1 1> /dev/null" + cd $_multi && $_php safe.php backfill $log; date +\"%D %T\"; $_sleep $backsleep' 2>&1 1> /dev/null" ); } else if (($backfill != 0) && ($kill_pp == "false") && (TIME() - $time6 <= 4800)) { $log = writelog($panes0[3]); @@ -1324,7 +1324,7 @@ while ($i > 0) { } else if (($backfill != 0) && ($kill_pp == "false") && (TIME() - $time6 >= 4800)) { $log = writelog($panes0[3]); shell_exec("tmux respawnp -k -t${tmux_session}:0.3 ' \ - $_python ${DIR}/../python/backfill_threaded.py all $log; date +\"%D %T\"; $_sleep $backsleep' 2>&1 1> /dev/null" + cd $_multi && $_php backfill.php $log; $log; date +\"%D %T\"; $_sleep $backsleep' 2>&1 1> /dev/null" ); $time6 = TIME(); } else if ($kill_pp == "true") { diff --git a/lib/DB/patches/0087~site.sql b/lib/DB/patches/0087~site.sql new file mode 100644 index 000000000..a146c1938 --- /dev/null +++ b/lib/DB/patches/0087~site.sql @@ -0,0 +1,6 @@ +INSERT IGNORE INTO `site` (`setting`, `value`) VALUES + ('safepartrepair','0'), + ('maxpartrepair', '15000'), + ('partrepairmaxtries', '3'), + ('tablepergroup', '0'); +UPDATE `tmux` SET `value` = '87' WHERE `setting` = 'sqlpatch'; diff --git a/lib/copy_this/misc/update_scripts/backfill.php b/lib/copy_this/misc/update_scripts/backfill.php new file mode 100644 index 000000000..6748ad0f7 --- /dev/null +++ b/lib/copy_this/misc/update_scripts/backfill.php @@ -0,0 +1,42 @@ + $pdo]); +if ($nntp->doConnect() !== true) { + exit($pdo->log->error("Unable to connect to usenet.")); +} + +if (isset($argv[1]) && $argv[1] == 'all' && $argv[1] !== 'safe' && $argv[1] !== 'alph' && $argv[1] !== 'date' && !is_numeric($argv[1]) && !isset($argv[2])) { + $backfill = new \Backfill(['NNTP' => $nntp, 'Settings' => $pdo]); + $backfill->backfillAllGroups(); +} else if (isset($argv[1]) && $argv[1] !== 'all' && $argv[1] !== 'safe' && $argv[1] !== 'alph' && $argv[1] !== 'date' && !is_numeric($argv[1]) && !isset($argv[2])) { + $backfill = new \Backfill(['NNTP' => $nntp, 'Settings' => $pdo]); + $backfill->backfillAllGroups($argv[1]); +} else if (isset($argv[1]) && $argv[1] !== 'all' && $argv[1] !== 'safe' && $argv[1] !== 'alph' && $argv[1] !== 'date' && !is_numeric($argv[1]) && isset($argv[2]) && is_numeric($argv[2])) { + $backfill = new \Backfill(['NNTP' => $nntp, 'Settings' => $pdo]); + $backfill->backfillAllGroups($argv[1], $argv[2]); +} else if (isset($argv[1]) && $argv[1] !== 'all' && $argv[1] !== 'safe' && $argv[1] == 'alph' && $argv[1] !== 'date' && !is_numeric($argv[1]) && isset($argv[2]) && is_numeric($argv[2])) { + $backfill = new \Backfill(['NNTP' => $nntp, 'Settings' => $pdo]); + $backfill->backfillAllGroups('', $argv[2], 'normal'); +} else if (isset($argv[1]) && $argv[1] !== 'all' && $argv[1] !== 'safe' && $argv[1] !== 'alph' && $argv[1] == 'date' && !is_numeric($argv[1]) && isset($argv[2]) && is_numeric($argv[2])) { + $backfill = new \Backfill(['NNTP' => $nntp, 'Settings' => $pdo]); + $backfill->backfillAllGroups('', $argv[2], 'date'); +} else if (isset($argv[1]) && $argv[1] !== 'all' && $argv[1] == 'safe' && $argv[1] !== 'alph' && $argv[1] !== 'date' && !is_numeric($argv[1]) && isset($argv[2]) && is_numeric($argv[2])) { + $backfill = new \Backfill(['NNTP' => $nntp, 'Settings' => $pdo]); + $backfill->safeBackfill($argv[2]); +} else { + exit($pdo->log->error("\nWrong set of arguments.\n" + . 'php backfill.php safe 200000 ...: Backfill an active group alphabetically, x articles, the script stops,' . "\n" + . ' ...: if the group has reached reached 2012-06-24, the next group will backfill.' . "\n" + . 'php backfill.php alph 200000 ...: Backfills all groups (sorted alphabetically) by number of articles' . "\n" + . 'php backfill.php date 200000 ...: Backfills all groups (sorted by least backfilled in time) by number of articles' . "\n" + . 'php backfill.php alt.binaries.ath 200000 ...: Backfills a group by name by number of articles' . "\n" + . 'php backfill.php all ...: Backfills all groups 1 at a time, by date (set in admin-view groups)' . "\n" + . 'php backfill.php alt.binaries.ath ...: Backfills a group by name, by date (set in admin-view groups)' . "\n")); +} \ No newline at end of file diff --git a/lib/copy_this/misc/update_scripts/nix_scripts/multiprocessing/.do_not_run/switch.php b/lib/copy_this/misc/update_scripts/nix_scripts/multiprocessing/.do_not_run/switch.php index a0bf9caca..e093f0cac 100644 --- a/lib/copy_this/misc/update_scripts/nix_scripts/multiprocessing/.do_not_run/switch.php +++ b/lib/copy_this/misc/update_scripts/nix_scripts/multiprocessing/.do_not_run/switch.php @@ -11,6 +11,7 @@ require_once(WWW_DIR . "/lib/releases.php"); require_once(WWW_DIR . "/lib/groups.php"); require_once(WWW_DIR . "/lib/backfill.php"); require_once(WWW_DIR . "/lib/nntp.php"); +require_once(WWW_DIR . "/lib/site.php"); require_once(NN_LIB . 'RequestIDLocal.php'); @@ -65,6 +66,8 @@ switch ($options[1]) { $pdo = new \DB(); $nntp = nntp($pdo); $groups = new \Groups(); + $s = new Sites(); + $site = $s->get(); $groupMySQL = $groups->getByName($options[3]); if ($nntp->isError($nntp->selectGroup($groupMySQL['name']))) { if ($nntp->isError($nntp->dataError($nntp, $groupMySQL['name']))) { @@ -72,7 +75,7 @@ switch ($options[1]) { } } $binaries = new \Binaries(); - $return = $binaries->scan($groupMySQL, $options[4], $options[5], ($site->safepartrepair == 1 ? 'update' : 'backfill')); + $return = $binaries->scan($nntp, $groupMySQL, $options[4], $options[5], ($site->safepartrepair == 1 ? 'update' : 'backfill')); if (empty($return)) { exit(); } diff --git a/lib/copy_this/www/lib/backfill.php b/lib/copy_this/www/lib/backfill.php index 5c800dde4..e7b3d7d2b 100644 --- a/lib/copy_this/www/lib/backfill.php +++ b/lib/copy_this/www/lib/backfill.php @@ -3,310 +3,405 @@ require_once(WWW_DIR."/lib/framework/db.php"); require_once(WWW_DIR."/lib/groups.php"); require_once(WWW_DIR."/lib/nntp.php"); require_once(WWW_DIR."/lib/binaries.php"); +require_once(WWW_DIR."/lib/Logger.php"); -/** - * Retrieves messages from usenet based on provided backfill-to date. - */ class Backfill { /** - * Default constructor. + * Instance of class Settings + * + * @var DB */ - function Backfill() + public $pdo; + + /** + * @var Binaries + */ + protected $_binaries; + + /** + * Instance of class ColorCLI. + * + * @var ColorCLI + */ + protected $_colorCLI; + + /** + * Instance of class debugging. + * + * @var Logger + */ + protected $_debugging; + + /** + * @var Groups + */ + protected $_groups; + + /** + * @var NNTP + */ + protected $_nntp; + /** + * Should we use compression for headers? + * + * @var bool + */ + protected $_compressedHeaders; + + /** + * Log and or echo debug. + * @var bool + */ + protected $_debug = false; + + /** + * Echo to cli? + * @var bool + */ + protected $_echoCLI; + + /** + * How far back should we go on safe back fill? + * + * @var string + */ + protected $_safeBackFillDate; + + /** + * @var string + */ + protected $_safePartRepair; + + /** + * Constructor. + * + * @param array $options Class instances / Echo to cli? + */ + public function __construct(array $options = []) { - $this->n = "\n"; + $defaults = [ + 'Echo' => true, + 'Logger' => null, + 'Groups' => null, + 'NNTP' => null, + 'Settings' => null + ]; + $options += $defaults; + + $this->_echoCLI = ($options['Echo'] && NN_ECHOCLI); + + $this->pdo = ($options['Settings'] instanceof DB ? $options['Settings'] : new \DB()); + $this->_groups = ($options['Groups'] instanceof \Groups ? $options['Groups'] : new \Groups(['Settings' => $this->pdo])); + $this->_nntp = ($options['NNTP'] instanceof \NNTP + ? $options['NNTP'] : new \NNTP(['Settings' => $this->pdo]) + ); + $s = new Sites(); + $this->site = $s->get(); + + $this->_debug = (NN_LOGGING || NN_DEBUG); + if ($this->_debug) { + try { + $this->_debugging = ($options['Logger'] instanceof \Logger ? $options['Logger'] : new \Logger(['ColorCLI' => $this->pdo->log])); + } catch (\LoggerException $error) { + $this->_debug = false; + } + } + + $this->_compressedHeaders = ($this->site->compressedheaders == 1 ? true : false); + $this->_safeBackFillDate = ($this->site->safebackfilldate != '') ? (string)$this->site->safebackfilldate : '2008-08-14'; + $this->_safePartRepair = ($this->site->safepartrepair == 1 ? 'update' : 'backfill'); } /** - * Update all active groups categories and descriptions. + * Backfill all the groups up to user specified time/date. + * + * @param string $groupName + * @param string|int $articles + * @param string $type + * + * @return void */ - function backfillAllGroups($groupName='', $backfillDate=null, $regexOnly=false) + public function backfillAllGroups($groupName = '', $articles ='', $type = '') { - $n = $this->n; - $groups = new Groups; - $res = false; - if ($groupName != '') - { - $grp = $groups->getByName($groupName); - if ($grp) - $res = array($grp); - } - else - { - $res = $groups->getActive(); + $res = []; + if ($groupName !== '') { + $grp = $this->_groups->getByName($groupName); + if ($grp) { + $res = [$grp]; + } + } else { + if ($type === 'normal' || $type === '') { + $res = $this->_groups->getActiveBackfill(); + } else if ($type === 'date') { + $res = $this->_groups->getActiveByDateBackfill(); + } } - if ($res) - { - foreach($res as $groupArr) - { - $this->backfillGroup($groupArr, $backfillDate, $regexOnly); - } - } - else - { - echo "No groups specified. Ensure groups are added to newznab's database for updating.$n"; - } - } - - /** - * Update a group back to a specified date. - */ - function backfillGroup($groupArr, $backfillDate=null, $regexOnly=false) - { - - $db = new DB(); - $binaries = new Binaries(); - $n = $this->n; - - if ($regexOnly === true) - { - echo "Only inserting binaries which match regex$n"; - $binaries->onlyProcessRegexBinaries = true; - } - - $this->startGroup = microtime(true); - - $nntp = new Nntp(); - $nntpc = new Nntp(); - //Make sure we actually have a connection going before doing anything. - if ($nntp->doConnect(5, false, true)) - { - - echo 'Processing '.$groupArr['name'].$n; - - $data = $nntp->selectGroup($groupArr['name']); - if(PEAR::isError($data)) - { - echo "Could not select group (bad name?): {$groupArr['name']}$n"; - return; - } - - if ($backfillDate) - $targetpost = $this->daytopost($nntp,$groupArr['name'],$this->dateToDays($backfillDate),TRUE); // get targetpost based on date - else - $targetpost = $this->daytopost($nntp,$groupArr['name'],$groupArr['backfill_target'],TRUE); //get targetpost based on days target - - if($groupArr['first_record'] == 0 || $groupArr['backfill_target'] == 0 && !$backfillDate) - { - echo "Group ".$groupArr['name']." has invalid numbers. Have you run update on it? Have you set the backfill days amount?$n"; - return; - } - - echo "Group ".$data["group"].": server has ".$data['first']." - ".$data['last'].", or ~"; - echo((int) (($this->postdate($nntp,$data['last'],FALSE) - $this->postdate($nntp,$data['first'],FALSE))/86400)); - echo " days.".$n."Local first = ".$groupArr['first_record']." ("; - echo((int) ((date('U') - $this->postdate($nntp,$groupArr['first_record'],FALSE))/86400)); - echo " days). Backfill target of ".($backfillDate ? date('Y-m-d', $backfillDate) : $groupArr['backfill_target']." days")." is post $targetpost.$n"; - - if($targetpost >= $groupArr['first_record']) //if our estimate comes back with stuff we already have, finish - { - echo "Nothing to do, we already have the target post.$n $n"; - return ""; - } - //get first and last part numbers from newsgroup - if($targetpost < $data['first']) - { - echo "WARNING: Backfill came back as before server's first. Setting targetpost to server first.$n"; - echo "Skipping Group $n"; - return ""; - } - - echo ($binaries->onlyProcessRegexBinaries===true) ? "Note: Discarding parts that do not match a regex".$n : ""; - - //Done with $nntp for now, close it to avoid timeouts. - $nntp->doQuit(); - $nntpc->doConnect(); - $datac = $nntpc->selectGroup($groupArr['name']); - if(PEAR::isError($datac)) - { - echo "Could not select group (bad name?): {$groupArr['name']}$n"; - return; - } - - //calculate total number of parts - $total = $groupArr['first_record'] - $targetpost; - $done = false; - //set first and last, moving the window by maxxMssgs - $last = $groupArr['first_record'] - 1; - $first = $last - $binaries->messagebuffer + 1; //set initial "chunk" - if($targetpost > $first) //just in case this is the last chunk we needed - $first = $targetpost; - while($done === false) - { - $binaries->startLoop = microtime(true); - - echo "Getting ".($last-$first+1)." parts (".(number_format($first-$targetpost))." in queue)".$n; - - flush(); - - $success = $binaries->scan($nntpc, $groupArr, $first, $last, 'backfill'); - - if (!$success) - { - return ""; - } - - $db->queryExec(sprintf("update groups SET first_record = %s, last_updated = now() WHERE ID = %d", $db->escapeString($first), $groupArr['ID'])); - - if($first==$targetpost) - $done = true; - else - { //Keep going: set new last, new first, check for last chunk. - $last = $first - 1; - $first = $last - $binaries->messagebuffer + 1; - if($targetpost > $first) - $first = $targetpost; - } - } - //Done with $nntpc - $nntpc->doQuit(); - //Just need $nntp for a quick check on the first_record_postdate - $nntp->doConnect(); - //$nntp->selectGroup($groupArr['name']); // some users report having this in keeps backfill working - $first_record_postdate = $this->postdate($nntp,$first,false); - //All done with NNTP. - $nntp->doQuit(); - if ($first_record_postdate != "") - $db->queryExec(sprintf("update groups SET first_record_postdate = FROM_UNIXTIME(".$first_record_postdate."), last_updated = now() WHERE ID = %d", $groupArr['ID'])); //Set group's first postdate - - $timeGroup = number_format(microtime(true) - $this->startGroup, 2); - echo "Group processed in $timeGroup seconds $n"; - } - else - { - echo "Failed to get NNTP connection.$n"; - } - } - - /** - * Returns single timestamp from a local article number. - */ - function postdate($nntp,$post,$debug=true) - { - $n = $this->n; - $attempts=0; - $date=""; - do - { - $msgs = $nntp->getOverview($post."-".$post,true,false); - if(PEAR::isError($msgs)) - { - echo "Error {$msgs->code}: {$msgs->message}$n"; - echo "Returning from postdate$n"; - return ""; + $groupCount = count($res); + if ($groupCount > 0) { + $counter = 1; + $allTime = microtime(true); + $dMessage = ( + 'Backfilling: ' . + $groupCount . + ' group(s) - Using compression? ' . + ($this->_compressedHeaders ? 'Yes' : 'No') + ); + if ($this->_debug) { + $this->_debugging->log('Backfill', "backfillAllGroups", $dMessage, \Logger::LOG_INFO); } - if(!isset($msgs[0]['Date']) || $msgs[0]['Date']=="" || is_null($msgs[0]['Date'])) - { - $success=false; + if ($this->_echoCLI) { + $this->pdo->log->doEcho($this->pdo->log->header($dMessage), true); + } + + $this->_binaries = new \Binaries( + ['NNTP' => $this->_nntp, 'Echo' => $this->_echoCLI, 'Settings' => $this->pdo, 'Groups' => $this->_groups] + ); + + if ($articles !== '' && !is_numeric($articles)) { + $articles = 20000; + } + + // Loop through groups. + foreach ($res as $groupArr) { + if ($groupName === '') { + $dMessage = "Starting group " . $counter . ' of ' . $groupCount; + if ($this->_debug) { + $this->_debugging->log('Backfill', "backfillAllGroups", $dMessage, \Logger::LOG_INFO); + } + + if ($this->_echoCLI) { + $this->pdo->log->doEcho($this->pdo->log->header($dMessage), true); + } + } + $this->backfillGroup($groupArr, $groupCount - $counter, $articles); + $counter++; + } + + $dMessage = 'Backfilling completed in ' . number_format(microtime(true) - $allTime, 2) . " seconds."; + if ($this->_debug) { + $this->_debugging->log('Backfill', "backfillAllGroups", $dMessage, \Logger::LOG_INFO); + } + + if ($this->_echoCLI) { + $this->pdo->log->doEcho($this->pdo->log->primary($dMessage)); + } + } else { + $dMessage = "No groups specified. Ensure groups are added to nZEDb's database for updating."; + if ($this->_debug) { + $this->_debugging->log('Backfill', "backfillAllGroups", $dMessage, \Logger::LOG_FATAL); + } + + if ($this->_echoCLI) { + $this->pdo->log->doEcho($this->pdo->log->warning($dMessage), true); + } + } + } + + /** + * Backfill single group. + * + * @param array $groupArr + * @param int $left + * @param int|string $articles + * + * @return void + */ + public function backfillGroup($groupArr, $left, $articles = '') + { + // Start time for this group. + $startGroup = microtime(true); + $groupName = str_replace('alt.binaries', 'a.b', $groupArr['name']); + + // If our local oldest article 0, it means we never ran update_binaries on the group. + if ($groupArr['first_record'] <= 0) { + $dMessage = + "You need to run update_binaries on " . + $groupName . + ". Otherwise the group is dead, you must disable it."; + if ($this->_debug) { + $this->_debugging->log('Backfill', "backfillGroup", $dMessage, \Logger::LOG_ERROR); + } + + if ($this->_echoCLI) { + $this->pdo->log->doEcho($this->pdo->log->error($dMessage)); + } + return; + } + + // Select group, here, only once + $data = $this->_nntp->selectGroup($groupArr['name']); + if ($this->_nntp->isError($data)) { + $data = $this->_nntp->dataError($this->_nntp, $groupArr['name']); + if ($this->_nntp->isError($data)) { + return; + } + } + + if ($this->_echoCLI) { + $this->pdo->log->doEcho($this->pdo->log->primary('Processing ' . $groupName), true); + } + + // Check if this is days or post backfill. + $postCheck = ($articles === '' ? false : true); + + // Get target post based on date or user specified number. + $targetpost = (string)($postCheck + ? + round($groupArr['first_record'] - $articles) + : + $this->_binaries->daytopost($groupArr['backfill_target'], $data) + ); + + // Check if target post is smaller than server's oldest, set it to oldest if so. + if ($targetpost < $data['first']) { + $targetpost = $data['first']; + } + + // Check if our target post is newer than our oldest post or if our local oldest article is older than the servers oldest. + if ($targetpost >= $groupArr['first_record'] || $groupArr['first_record'] <= $data['first']) { + $dMessage = + "We have hit the maximum we can backfill for " . + $groupName . + ", skipping it, consider disabling backfill on it."; + if ($this->_debug) { + $this->_debugging->log('Backfill', "backfillGroup", $dMessage, \Logger::LOG_NOTICE); + } + + if ($this->_echoCLI) { + $this->pdo->log->doEcho($this->pdo->log->notice($dMessage), true); + } + return; + } + + if ($this->_echoCLI) { + $this->pdo->log->doEcho( + $this->pdo->log->primary( + 'Group ' . + $groupName . + "'s oldest article is " . + number_format($data['first']) . + ', newest is ' . + number_format($data['last']) . + ".\nOur target article is " . + number_format($targetpost) . + '. Our oldest article is article ' . + number_format($groupArr['first_record']) . + '.' + ) + ); + } + + // Set first and last, moving the window by max messages. + $last = (string)($groupArr['first_record'] - 1); + + // Set the initial "chunk". + $first = (string)($last - $this->_binaries->messageBuffer + 1); + + // Just in case this is the last chunk we needed. + if ($targetpost > $first) { + $first = $targetpost; + } + + $done = false; + while ($done === false) { + + if ($this->_echoCLI) { + $this->pdo->log->doEcho( + $this->pdo->log->set256('Yellow') . + "\nGetting " . + (number_format($last - $first + 1)) . + " articles from " . + $groupName . + ", " . + $left . + " group(s) left. (" . + (number_format($first - $targetpost)) . + " articles in queue)." . + $this->pdo->log->rsetColor(), true + ); + } + + flush(); + $lastMsg = $this->_binaries->scan($this->_nntp, $groupArr, $first, $last, $this->_safePartRepair); + + // Get the oldest date. + if (isset($lastMsg['firstArticleDate'])) { + // Try to get it from the oldest pulled article. + $newdate = strtotime($lastMsg['firstArticleDate']); } else { - $date = $msgs[0]['Date']; - $success=true; + // If above failed, try to get it with postdate method. + $newdate = $this->_binaries->postdate($first, $data); } - if($debug && $attempts > 0) echo "retried $attempts time(s)".$n; - $attempts++; - } while($attempts <= 3 && $success == false); - if (!$success) { return ""; } + $this->pdo->queryExec( + sprintf(' + UPDATE groups + SET first_record_postdate = %s, first_record = %s, last_updated = NOW() + WHERE ID = %d', + $this->pdo->from_unixtime($newdate), + $this->pdo->escapeString($first), + $groupArr['ID']) + ); + if ($first == $targetpost) { + $done = true; + } else { + // Keep going: set new last, new first, check for last chunk. + $last = (string)($first - 1); + $first = (string)($last - $this->_binaries->messageBuffer + 1); + if ($targetpost > $first) { + $first = $targetpost; + } + } + } - if($debug) echo "DEBUG: postdate for post: $post came back $date ("; - $date = strtotime($date); - if($debug) echo "$date seconds unixtime or ".$this->daysOld($date)." days)".$n; - return $date; + if ($this->_echoCLI) { + $this->pdo->log->doEcho( + $this->pdo->log->primary( + PHP_EOL . + 'Group ' . + $groupName . + ' processed in ' . + number_format(microtime(true) - $startGroup, 2) . + " seconds." + ), true + ); + } } /** - * Calculates the post number for a given number of days back in a group. + * Safe backfill using posts. Going back to a date specified by the user on the site settings. + * This does 1 group for x amount of parts until it reaches the date. + * + * @param string $articles + * + * @return void */ - function daytopost($nntp, $group, $days, $debug=true) + public function safeBackfill($articles = '') { - $n = $this->n; - $pddebug = false; //DEBUG every postdate call?!?! - if ($debug) echo "INFO: daytopost finding post for $group $days days back.".$n; + $groupname = $this->pdo->queryOneRow( + sprintf(' + SELECT name FROM groups + WHERE first_record_postdate BETWEEN %s AND NOW() + AND backfill = 1 + ORDER BY name ASC', + $this->pdo->escapeString($this->_safeBackFillDate) + ) + ); - $data = $nntp->selectGroup($group); - if(PEAR::isError($data)) - { - echo "Error {$data->code}: {$data->message}$n"; - echo "Returning from daytopost$n"; - return ""; - } - $goaldate = date('U')-(86400*$days); //goaltimestamp - $totalnumberofarticles = $data['last'] - $data['first']; - $upperbound = $data['last']; - $lowerbound = $data['first']; - - if ($debug) echo "Total Articles: $totalnumberofarticles $n Upper: $upperbound $n Lower: $lowerbound $n Goal: ".date("r", $goaldate)." ($goaldate) $n"; - if ($data['last']==PHP_INT_MAX) { echo "ERROR: Group data is coming back as php's max value. You should not see this since we use a patched Net_NNTP that fixes this bug.$n"; die(); } - - $firstDate = $this->postdate($nntp, $data['first'], $pddebug); - $lastDate = $this->postdate($nntp, $data['last'], $pddebug); - if ($goaldate < $firstDate) - { - echo "WARNING: Backfill target of $days day(s) is older than the first article stored on your news server.$n"; - echo "Starting from the first available article (".date("r", $firstDate)." or ".$this->daysOld($firstDate)." days).$n"; - return $data['first']; - } - elseif ($goaldate > $lastDate) - { - echo "ERROR: Backfill target of $days day(s) is newer than the last article stored on your news server.$n"; - echo "To backfill this group you need to set Backfill Days to at least ".ceil($this->daysOld($lastDate)+1)." days (".date("r", $lastDate-86400).").$n"; - return ""; - } - if ($debug) echo "DEBUG: Searching for postdate $n Goaldate: $goaldate (".date("r", $goaldate).") $n Firstdate: $firstDate (".((is_int($firstDate))?date("r", $firstDate):'n/a').") $n Lastdate: $lastDate (".date("r", $lastDate).") $n"; - - $interval = floor(($upperbound - $lowerbound) * 0.5); - $dateofnextone = ""; - $templowered = ""; - - if ($debug) echo "Start: ".$data['first']." $n End: ".$data['last']." $n Interval: $interval $n"; - - $dateofnextone = $lastDate; - - while($this->daysOld($dateofnextone) < $days) //match on days not timestamp to speed things up - { - $nskip = 1; - while(($tmpDate = $this->postdate($nntp,($upperbound-$interval),$pddebug))>$goaldate) - { - $upperbound = $upperbound - $interval - ($nskip - 1); - if($debug) echo "New upperbound ($upperbound) is ".$this->daysOld($tmpDate)." days old. $n"; - $nskip = $nskip * 2; + if (!$groupname) { + $dMessage = + 'No groups to backfill, they are all at the target date ' . + $this->_safeBackFillDate . + ", or you have not enabled them to be backfilled in the groups page.\n"; + if ($this->_debug) { + $this->_debugging->log('Backfill', "safeBackfill", $dMessage, \Logger::LOG_FATAL); } - if(!$templowered) - { - $interval = ceil(($interval/2)); - if ($interval <= 0) break; - if($debug) echo "Set interval to $interval articles. $n"; - } - $dateofnextone = $this->postdate($nntp,($upperbound-1),$pddebug); - - $skip = 1; - while(!$dateofnextone) - { - $upperbound = $upperbound - $skip; - $skip = $skip * 2; - if($debug) echo "Getting next article date... $upperbound\n"; - $dateofnextone = $this->postdate($nntp,($upperbound-1),$pddebug); - } - } - echo "Determined to be article $upperbound which is ".$this->daysOld($dateofnextone)." days old (".date("r", $dateofnextone).") $n"; - return $upperbound; + exit($dMessage); + } else { + $this->backfillAllGroups($groupname['name'], $articles); + } } - - /** - * Calculate the number of days from a timestamp. - */ - private function daysOld($timestamp) - { - return round((time()-$timestamp)/86400, 1); - } - - /** - * Calculate the number of days from a date. - */ - private function dateToDays($backfillDate) - { - return floor(-($backfillDate - time())/(60*60*24)); - } -} +} \ No newline at end of file diff --git a/lib/copy_this/www/lib/binaries.php b/lib/copy_this/www/lib/binaries.php index 6d6a1df31..d99fba539 100644 --- a/lib/copy_this/www/lib/binaries.php +++ b/lib/copy_this/www/lib/binaries.php @@ -3,6 +3,8 @@ require_once(WWW_DIR . "/lib/framework/db.php"); require_once(WWW_DIR . "/lib/nntp.php"); require_once(WWW_DIR . "/lib/groups.php"); require_once(WWW_DIR . "/lib/backfill.php"); +require_once(WWW_DIR . "/lib/ColorCLI.php"); +require_once(WWW_DIR . "/lib/Logger.php"); /** * This class manages the downloading of binaries and parts from usenet, and the @@ -20,6 +22,13 @@ class Binaries const BLACKLIST_FIELD_FROM = 2; const BLACKLIST_FIELD_MESSAGEID = 3; + /** + * How many headers do we download per loop? + * + * @var int + */ + public $messageBuffer; + /** * Default constructor */ @@ -29,18 +38,33 @@ class Binaries $s = new Sites(); $site = $s->get(); + $this->messageBuffer = ($site->maxmssgs != '') ? $site->maxmssgs : 20000; $this->compressedHeaders = ($site->compressedheaders == "1") ? true : false; $this->messagebuffer = (!empty($site->maxmssgs)) ? $site->maxmssgs : 20000; $this->MaxMsgsPerRun = (!empty($site->maxmsgsperrun)) ? $site->maxmsgsperrun : 200000; $this->NewGroupScanByDays = ($site->newgroupscanmethod == "1") ? true : false; $this->NewGroupMsgsToScan = (!empty($site->newgroupmsgstoscan)) ? $site->newgroupmsgstoscan : 50000; $this->NewGroupDaysToScan = (!empty($site->newgroupdaystoscan)) ? $site->newgroupdaystoscan : 3; + $this->_tablePerGroup = ($site->tablepergroup == 1 ? true : false); $this->blackList = array(); //cache of our black/white list $this->blackList_by_group = array(); $this->message = array(); $this->onlyProcessRegexBinaries = false; + $this->_colorCLI = new \ColorCLI(); + $this->_echoCLI = NN_ECHOCLI; + $this->_pdo = new DB(); + $this->_nntp = new \NNTP(['Echo' => $this->_colorCLI, 'Settings' => $this->_pdo, 'ColorCLI' => $this->_colorCLI]); + $this->_groups = new Groups(); + $this->_debug = (NN_DEBUG || NN_LOGGING); + if ($this->_debug) { + try { + $this->_debugging = new \Logger(['ColorCLI' => $this->_colorCLI]); + } catch (\LoggerException $error) { + $this->_debug = false; + } + } } /** @@ -210,7 +234,7 @@ class Binaries } } - $last_record_postdate = $backfill->postdate($nntp, $last, false); + $last_record_postdate = $this->postdate($last, $data); if ($last_record_postdate != "") { $db->queryExec(sprintf("update groups SET last_record_postdate = FROM_UNIXTIME(" . $last_record_postdate . "), last_updated = now() WHERE ID = %d", $groupArr['ID'])); //Set group's last postdate } @@ -831,4 +855,214 @@ class Binaries return $result; } + + /** + * Returns article number based on # of days. + * + * @param int $days How many days back we want to go. + * @param array $data Group data from usenet. + * + * @return string + */ + public function daytopost($days, $data) + { + $goalTime = // The time we want = + time() // current unix time (ex. 1395699114) + - // minus + (86400 * $days); // 86400 (seconds in a day) times days wanted. (ie 1395699114 - 2592000 (30days)) = 1393107114 + + // The servers oldest date. + $firstDate = $this->postdate($data['first'], $data); + if ($goalTime < $firstDate) { + // If the date we want is older than the oldest date in the group return the groups oldest article. + return $data['first']; + } + + // The servers newest date. + $lastDate = $this->postdate($data['last'], $data); + if ($goalTime > $lastDate) { + // If the date we want is newer than the groups newest date, return the groups newest article. + return $data['last']; + } + + $totalArticles = (int)($lastDate - $firstDate); + + if ($this->_echoCLI) { + $this->_colorCLI->doEcho( + $this->_colorCLI->primary( + 'Searching for an approximate article number for group ' . $data['group'] . ' ' . $days . ' days back.' + ) + ); + } + + switch (true) { + case $totalArticles < 1000000: + $matchPercentage = 1.0100; + break; + case $totalArticles < 10000000: + $matchPercentage = 1.0070; + + break; + case $totalArticles < 100000000: + $matchPercentage = 1.0030; + break; + case $totalArticles < 500000000: + $matchPercentage = 1.0010; + break; + case $totalArticles < 1000000000: + $matchPercentage = 1.0008; + break; + default: + $matchPercentage = 1.0005; + break; + } + + $wantedArticle = ($data['last'] * (($goalTime - $firstDate) / ($totalArticles))); + $articleTime = 0; + $percent = 1.01; + for ($i = 0; $i < 100; $i++) { + $wantedArticle = (int)$wantedArticle; + + if ($wantedArticle <= $data['first'] || $wantedArticle >= $data['last']) { + break; + } + + $articleTime = $this->postdate($wantedArticle, $data); + if ($articleTime >= ($goalTime / $matchPercentage) && $articleTime <= ($goalTime * $matchPercentage)) { + break; + } + + if ($articleTime > $goalTime) { + $wantedArticle /= $percent; + } else if ($articleTime < $goalTime) { + $wantedArticle *= $percent; + } + $percent -= 0.001; + } + + $wantedArticle = (int)$wantedArticle; + if ($this->_echoCLI) { + $this->_colorCLI->doEcho( + $this->_colorCLI->primary( + 'Found article #' . $wantedArticle . ' which has a date of ' . date('r', $articleTime) . + ', vs wanted date of ' . date('r', $goalTime) . '.' + ) + ); + } + + return $wantedArticle; + } + + /** + * Convert unix time to days ago. + * + * @param int $timestamp unix time + * + * @return float + */ + private function daysOld($timestamp) + { + return round((time() - (!is_numeric($timestamp) ? strtotime($timestamp) : $timestamp)) / 86400, 1); + } + + /** + * Returns unix time for an article number. + * + * @param int $post The article number to get the time from. + * @param array $groupData Usenet group info from NNTP selectGroup method. + * + * @return bool|int + */ + public function postdate($post, array $groupData) + { + // Set table names + $groupID = $this->_groups->getIDByName($groupData['group']); + $group = []; + if ($groupID !== '') { + $group = $this->_groups->getCBPTableNames($this->_tablePerGroup, $groupID); + } + + $currentPost = $post; + + $attempts = $date = 0; + do { + // Try to get the article date locally first. + if ($groupID !== '') { + // Try to get locally. + $local = $this->_pdo->queryOneRow( + sprintf(' + SELECT b.date AS date + FROM %s b, %s p + WHERE b.ID = p.binaryID + AND b.groupID = %s + AND p.number = %s LIMIT 1', + $group['bname'], + $group['pname'], + $groupID, + $currentPost + ) + ); + if ($local !== false) { + $date = $local['date']; + break; + } + } + + // If we could not find it locally, try usenet. + $header = $this->_nntp->getXOVER($currentPost); + if (!$this->_nntp->isError($header)) { + // Check if the date is set. + if (isset($header[0]['Date']) && strlen($header[0]['Date']) > 0) { + $date = $header[0]['Date']; + break; + } + } + + // Try to get a different article number. + if (abs($currentPost - $groupData['first']) > abs($groupData['last'] - $currentPost)) { + $tempPost = round($currentPost / (mt_rand(1005, 1012) / 1000), 0, PHP_ROUND_HALF_UP); + if ($tempPost < $groupData['first']) { + $tempPost = $groupData['first']; + } + } else { + $tempPost = round((mt_rand(1005, 1012) / 1000) * $currentPost, 0, PHP_ROUND_HALF_UP); + if ($tempPost > $groupData['last']) { + $tempPost = $groupData['last']; + } + } + // If we got the same article number as last time, give up. + if ($tempPost === $currentPost) { + break; + } + $currentPost = $tempPost; + + if ($this->_debug) { + $this->_colorCLI->doEcho($this->_colorCLI->debug('Postdate retried ' . $attempts . " time(s).")); + } + } while ($attempts++ <= 20); + + // If we didn't get a date, set it to now. + if (!$date) { + $date = time(); + } else { + $date = strtotime($date); + } + + if ($this->_debug) { + $this->_debugging->log( + 'Binaries', + "postdate", + 'Article (' . + $post . + "'s) date is (" . + $date . + ') (' . + $this->daysOld($date) . + " days old)", + \Logger::LOG_INFO + ); + } + + return $date; + } } \ No newline at end of file diff --git a/lib/copy_this/www/lib/groups.php b/lib/copy_this/www/lib/groups.php index 553c286fc..86314ed18 100644 --- a/lib/copy_this/www/lib/groups.php +++ b/lib/copy_this/www/lib/groups.php @@ -321,4 +321,71 @@ class Groups $db->queryExec(sprintf("UPDATE groups SET backfill = %d WHERE id = %d", $status, $id)); return "Group $id has been " . (($status == 0) ? 'deactivated' : 'activated') . '.'; } + + /** + * @return array + */ + public function getActiveBackfill() + { + $db = new DB(); + return $db->query("SELECT * FROM groups WHERE backfill = 1 AND last_record != 0 ORDER BY name"); + } + + /** + * @return array + */ + public function getActiveByDateBackfill() + { + $db = new DB(); + return $db->query("SELECT * FROM groups WHERE backfill = 1 AND last_record != 0 ORDER BY first_record_postdate DESC"); + } + + /** + * @var array + */ + private $cbppTableNames; + + /** + * Get the names of the binaries/parts/part repair tables. + * If TPG is on, try to create new tables for the group_id, if we fail, log the error and exit. + * + * @param bool $tpgSetting false, tpg is off in site setting, true tpg is on in site setting. + * @param int $groupID ID of the group. + * + * @return array The table names. + */ + public function getCBPTableNames($tpgSetting, $groupID) + { + $groupKey = ($groupID); + + // Check if buffered and return. Prevents re-querying MySQL when TPG is on. + if (isset($this->cbppTableNames[$groupKey])) { + return $this->cbppTableNames[$groupKey]; + } + + $tables = []; + $tables['bname'] = 'binaries'; + $tables['pname'] = 'parts'; + $tables['prname'] = 'partrepair'; + + if ($tpgSetting === true) { + if ($groupID == '') { + exit('Error: You must use releases_threaded.py since you have enabled TPG!'); + } + + if ($this->createNewTPGTables($groupID) === false && NN_ECHOCLI) { + exit('There is a problem creating new TPG tables for this group ID: ' . $groupID . PHP_EOL); + } + + $groupEnding = '_' . $groupID; + $tables['bname'] .= $groupEnding; + $tables['pname'] .= $groupEnding; + $tables['prname'] .= $groupEnding; + } + + // Buffer. + $this->cbppTableNames[$groupKey] = $tables; + + return $tables; + } } \ No newline at end of file diff --git a/lib/copy_this/www/templates/nntmux/styles/admin.css b/lib/copy_this/www/templates/nntmux/styles/admin.css index f7327875c..5433f3e35 100644 --- a/lib/copy_this/www/templates/nntmux/styles/admin.css +++ b/lib/copy_this/www/templates/nntmux/styles/admin.css @@ -30,14 +30,20 @@ div#message { /* deactivate link */ div#group_list a.group_active, div#group_list a.group_active:visited, -div#group_list a.group_active:active { +div#group_list a.group_active:active +a.backfill_active, +a.backfill_active:visited, +a.backfill_active:active { color: #CC0000; } /* activate link */ div#group_list a.group_deactive div#group_list a.group_deactive:visited, -div#group_list a.group_deactive:active { +div#group_list a.group_deactive:active +a.backfill_deactive, +a.backfill_deactive:visited, +a.backfill_deactive:active { color: #008C00; } diff --git a/lib/copy_this/www/templates/nntmux/views/admin/site-edit.tpl b/lib/copy_this/www/templates/nntmux/views/admin/site-edit.tpl index bdd9d0d56..7f06c6105 100644 --- a/lib/copy_this/www/templates/nntmux/views/admin/site-edit.tpl +++ b/lib/copy_this/www/templates/nntmux/views/admin/site-edit.tpl @@ -984,6 +984,31 @@ +