Files
newznab-tmux/lib/functions.php
T
2014-06-16 13:33:00 +02:00

1844 lines
51 KiB
PHP

<?php
require_once(dirname(__FILE__) . "/../bin/config.php");
require_once(WWW_DIR . "/lib/framework/db.php");
require_once(WWW_DIR . "/lib/category.php");
require_once(WWW_DIR . "/lib/releases.php");
require_once(WWW_DIR . "/lib/releaseimage.php");
require_once(WWW_DIR . "/lib/nzb.php");
require_once(WWW_DIR . "/lib/rarinfo/par2info.php");
require_once(WWW_DIR . "/lib/rarinfo/archiveinfo.php");
require_once(WWW_DIR . "/lib/rarinfo/zipinfo.php");
require_once(WWW_DIR . "/lib/nfo.php");
require_once(WWW_DIR . "/lib/site.php");
require_once(WWW_DIR . "/lib/util.php");
require_once(WWW_DIR . "/lib/groups.php");
require_once(WWW_DIR . '/lib/nntp.php');
require_once(WWW_DIR . "/lib/postprocess.php");
require_once(WWW_DIR . "/lib/Tmux.php");
require_once("consoletools.php");
require_once("ColorCLI.php");
require_once("nzbcontents.php");
require_once("namefixer.php");
require_once("TraktTv.php");
require_once("Enzebe.php");
//*addedd from nZEDb for testing
class Functions
{
function __construct($echooutput = true)
{
$s = new Sites();
$this->site = $s->get();
$t = new Tmux();
$this->tmux = $t->get();
$this->p = new Postprocess();
$this->echooutput = $echooutput;
$this->c = new ColorCLI();
$this->db = new DB();
$this->m = new Movie();
$this->consoleTools = new ConsoleTools();
$this->tmpPath = $this->site->tmpunrarpath;
$this->sleeptime = (!empty($this->tmux->amazonsleep)) ? $this->tmux->amazonsleep : 1000;
$this->DEBUG_ECHO = ($this->tmux->debuginfo == '0') ? false : true;
if (defined('DEBUG_ECHO') && DEBUG_ECHO == true) {
$this->DEBUG_ECHO = true;
}
$this->debug = ($this->tmux->debuginfo == "0") ? false : true;
$this->compressedHeaders = ($this->site->compressedheaders == '1') ? true : false;
$this->safepartrepair = (!empty($this->tmux->safepartrepair)) ? $this->tmux->safepartrepair : 0;
$this->safebdate = (!empty($this->tmux->safebackfilldate)) ? $this->tmux->safebackfilldate : '2012 - 06 - 24';
$this->DoPartRepair = ($this->tmux->partrepair == '0') ? false : true;
$this->messagebuffer = (!empty($this->site->maxmssgs)) ? (int)$this->site->maxmssgs : 20000;
$this->NewGroupScanByDays = ($this->site->newgroupscanmethod == '1') ? true : false;
$this->NewGroupMsgsToScan = (!empty($this->site->newgroupmsgstoscan)) ? (int)$this->site->newgroupmsgstoscan : 50000;
$this->NewGroupDaysToScan = (!empty($this->site->newgroupdaystoscan)) ? (int)$this->site->newgroupdaystoscan : 3;
$this->partrepairlimit = (!empty($this->tmux->maxpartrepair)) ? (int)$this->tmux->maxpartrepair : 15000;
}
/**
* @var object Instance of PDO class.
*/
private
static $pdo = null;
/**
* Should we use part repair?
*
* @var bool
*/
private
$DoPartRepair;
/**
* How many headers do we download per loop?
*
* @var int
*/
public
$messagebuffer;
/**
* How many days to go back on a new group?
*
* @var bool
*/
private
$NewGroupScanByDays;
/**
* Path to save large jpg pictures(xxx).
*
* @var string
*/
public
$jpgSavePath;
// database function
public
function queryArray($query)
{
$db = new DB();
if ($query == '') return false;
$result = $db->queryDirect($query);
$rows = array();
foreach ($result as $row) {
$rows[] = $row;
}
return (!isset($rows)) ? false : $rows;
}
// Used for deleting, updating (and inserting without needing the last insert ID).
public
function exec($query)
{
if ($query == '')
return false;
try {
$run = self::$pdo->prepare($query);
$run->execute();
return $run;
} catch (PDOException $e) {
// Deadlock or lock wait timeout, try 10 times.
$i = 1;
while (($e->errorInfo[1] == 1213 || $e->errorInfo[0] == 40001 || $e->errorInfo[1] == 1205 || $e->getMessage() == 'SQLSTATE[40001]: Serialization failure: 1213 Deadlock found when trying to get lock; try restarting transaction') && $i <= 10) {
echo $this->c->error("A Deadlock or lock wait timeout has occurred, sleeping.\n");
$this->consoletools->showsleep($i * $i);
$run = self::$pdo->prepare($query);
$run->execute();
return $run;
$i++;
}
if ($e->errorInfo[1] == 1213 || $e->errorInfo[0] == 40001 || $e->errorInfo[1] == 1205) {
//echo "Error: Deadlock or lock wait timeout.";
return false;
} else if ($e->errorInfo[1] == 1062 || $e->errorInfo[0] == 23000) {
//echo "\nError: Update would create duplicate row, skipping\n";
return false;
} else if ($e->errorInfo[1] == 1406 || $e->errorInfo[0] == 22001) {
//echo "\nError: Too large to fit column length\n";
return false;
} else
echo $this->c->error($e->getMessage());
return false;
}
}
public
function Prepare($query, $options = array())
{
try {
$PDOstatement = self::$pdo->prepare($query, $options);
} catch (PDOException $e) {
//echo $this->c->error($e->getMessage());
$PDOstatement = false;
}
return $PDOstatement;
}
public
function from_unixtime($utime, $escape = true)
{
if ($escape === true) {
return 'FROM_UNIXTIME(' . $utime . ')';
} else
return date('Y-m-d h:i:s', $utime);
}
// Date to unix time.
// (substitute for mysql's UNIX_TIMESTAMP() function)
public
function unix_timestamp($date)
{
return strtotime($date);
}
public
function unix_timestamp_column($column, $outputName = 'unix_time')
{
return 'UNIX_TIMESTAMP(' . $column . ') AS ' . $outputName;
}
/**
* Interpretation of mysql's UUID method.
* Return uuid v4 string. http://www.php.net/manual/en/function.uniqid.php#94959
*
* @return string
*/
public
function uuid()
{
return sprintf(
'%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
mt_rand(0, 0xffff),
mt_rand(0, 0xffff),
mt_rand(0, 0xffff),
mt_rand(0, 0x0fff) | 0x4000,
mt_rand(0, 0x3fff) | 0x8000,
mt_rand(0, 0xffff),
mt_rand(0, 0xffff),
mt_rand(0, 0xffff)
);
}
// gets name of category from category.php
public
function getNameByID($ID)
{
$db = new DB();
$parent = $db->queryOneRow(sprintf("SELECT title FROM category WHERE ID = %d", substr($ID, 0, 1) . "000"));
$cat = $db->queryOneRow(sprintf("SELECT title FROM category WHERE ID = %d", $ID));
return $parent["title"] . " " . $cat["title"];
}
public
function getIDByName($name)
{
$db = new DB();
$res = $db->queryOneRow(sprintf("SELECT ID FROM groups WHERE name = %s", $db->escapeString($name)));
return $res["ID"];
}
//deletes from releases
public
function fastDelete($ID, $guid)
{
$nzb = new Enzebe();
$ri = new ReleaseImage();
$ri->delete($guid);
//
// delete from disk.
//
$nzbpath = $nzb->getNZBPath($guid);
if (file_exists($nzbpath))
unlink($nzbpath);
$this->db->exec(sprintf("delete releases, releasenfo, releasecomment, usercart, releasefiles, releaseaudio, releasesubs, releasevideo, releaseextrafull
from releases
LEFT OUTER JOIN releasenfo on releasenfo.releaseID = releases.ID
LEFT OUTER JOIN releasecomment on releasecomment.releaseID = releases.ID
LEFT OUTER JOIN usercart on usercart.releaseID = releases.ID
LEFT OUTER JOIN releasefiles on releasefiles.releaseID = releases.ID
LEFT OUTER JOIN releaseaudio on releaseaudio.releaseID = releases.ID
LEFT OUTER JOIN releasesubs on releasesubs.releaseID = releases.ID
LEFT OUTER JOIN releasevideo on releasevideo.releaseID = releases.ID
LEFT OUTER JOIN releaseextrafull on releaseextrafull.releaseID = releases.ID
where releases.ID = %d", $ID
)
);
}
//reads name of group
public
function getByNameByID($ID)
{
$db = new DB();
$res = $db->queryOneRow(sprintf("select name from groups where ID = %d ", $ID));
return $res["name"];
}
// Sends releases back to other->misc.
public
function resetCategorize($where = '')
{
$this->db->exec('UPDATE releases SET categoryID = 8010, iscategorized = 0 ' . $where);
}
// Categorizes releases.
// $type = name or searchname
// Returns the quantity of categorized releases.
public
function categorizeRelease($type, $where = '', $echooutput = false)
{
$cat = new Category();
$relcount = 0;
$resrel = $this->db->queryDirect('SELECT ID, ' . $type . ', groupID FROM releases ' . $where);
$total = $resrel->rowCount();
if (count($resrel) > 0) {
foreach ($resrel as $rowrel) {
$catId = $cat->determineCategory($rowrel['groupID'], $rowrel[$type]);
$this->db->exec(sprintf('UPDATE releases SET categoryID = %d, iscategorized = 1 WHERE ID = %d', $catId, $rowrel['ID']));
$relcount++;
if ($this->echooutput) {
$this->consoleTools->overWritePrimary('Categorizing: ' . $this->consoleTools->percentString($relcount, $total));
}
}
}
if ($this->echooutput !== false && $relcount > 0) {
echo "\n";
}
return $relcount;
}
// Optimises/repairs tables on mysql.
public
function optimise($admin = false, $type = '')
{
$db = new DB();
$c = new ColorCLI();
$tablecnt = 0;
if ($type === 'true' || $type === 'full' || $type === 'analyze') {
$alltables = $db->query('SHOW TABLE STATUS');
} else {
$alltables = $db->query('SHOW TABLE STATUS WHERE Data_free / Data_length > 0.005');
}
$tablecnt = count($alltables);
if ($type === 'all' || $type === 'full') {
$tbls = '';
foreach ($alltables as $table) {
$tbls .= $table['Name'] . ', ';
}
$tbls = rtrim(trim($tbls), ',');
if ($admin === false) {
echo $this->c->primary('Optimizing tables: ' . $tbls);
}
$db->queryDirect("OPTIMIZE LOCAL TABLE ${tbls}");
} else {
foreach ($alltables as $table) {
if ($type === 'analyze') {
if ($admin === false) {
echo $this->c->primary('Analyzing table: ' . $table['Name']);
}
$db->queryDirect('ANALYZE LOCAL TABLE `' . $table['Name'] . '`');
} else {
if ($admin === false) {
echo $this->c->primary('Optimizing table: ' . $table['Name']);
}
if (strtolower($table['engine']) == 'myisam') {
$db->queryDirect('REPAIR TABLE `' . $table['Name'] . '`');
}
$db->queryDirect('OPTIMIZE LOCAL TABLE `' . $table['Name'] . '`');
}
}
}
if ($type !== 'analyze') {
$db->queryDirect('FLUSH TABLES');
}
return $tablecnt;
}
function doecho($str)
{
if ($this->echooutput)
echo $this->c->header($str);
}
// Convert 2012-24-07 to 2012-07-24, there is probably a better way
public
function checkDate($date)
{
if (!empty($date) && $date != null) {
$chk = explode(" ", $date);
$chkd = explode("-", $chk[0]);
if ($chkd[1] > 12) {
$date = date('Y-m-d H:i:s', strtotime($chkd[1] . " " . $chkd[2] . " " . $chkd[0]));
}
return $date;
}
return null;
}
public
function updateReleaseHasPreview($guid)
{
$this->db->exec(sprintf('UPDATE releases SET haspreview = 1 WHERE guid = %s', $this->db->escapeString($guid)));
}
public
function debug($str)
{
if ($this->echooutput && $this->DEBUG_ECHO) {
echo $this->c->debug($str);
}
}
/**
* @param string $group
* @param int $first
* @param int $type
* @param object $nntp
*
* @return void
*/
function getFinal($group, $first, $type, $nntp)
{
if (!isset($nntp)) {
exit($this->c->error("Not connected to usenet(backfill->getFinal).\n"));
}
$db = $this->db;
$groups = new Groups();
$groupArr = $groups->getByName($group);
// Select group, here, only once
$data = $nntp->selectGroup($groupArr['name']);
if ($nntp->isError($data)) {
$data = $nntp->dataError($nntp, $groupArr['name']);
if ($nntp->isError($data)) {
return;
}
}
if ($type == 'Backfill') {
$postsdate = $this->postdate($nntp, $first, false, $group, true, 'oldest');
} else {
$postsdate = $this->postdate($nntp, $first, false, $group, true, 'newest');
}
$postsdate = $this->from_unixtime($postsdate);
if ($type == 'Backfill') {
$db->exec(sprintf('UPDATE groups SET first_record_postdate = %s, first_record = %s, last_updated = NOW() WHERE ID = %d', $postsdate, $db->escapeString($first), $groupArr['ID']));
} else {
$db->exec(sprintf('UPDATE groups SET last_record_postdate = %s, last_record = %s, last_updated = NOW() WHERE ID = %d', $postsdate, $db->escapeString($first), $groupArr['ID']));
}
$this->doecho(
$type .
' Safe Threaded for ' .
$group .
" completed." .
$this->c->rsetColor()
);
}
/**
* Returns a single timestamp from a local article number.
* If the article is missing, you can pass $old as true to return false (then use the last known date).
*
* @param object $nntp
* @param int $post
* @param bool $debug
* @param string $group
* @param bool $old
* @param string $type
*
* @return bool|int
*/
public
function postdate($nntp, $post, $debug = true, $group, $old = false, $type)
{
if (!isset($nntp)) {
$dmessage = "Not connected to usenet(backfill->postdate).";
$this->c->error($dmessage);
return false;
}
$db = $this->db;
$keeppost = $post;
$attempts = 0;
$success = $record = false;
do {
$msgs = $nntp->getOverview($post . "-" . $post, true, false);
$attempts++;
if (!$nntp->isError($msgs)) {
// Set table names
$groups = new Groups();
$groupID = $this->getIDByName($group);
$groupa = array();
$groupa['bname'] = 'binaries';
$groupa['pname'] = 'parts';
if ((!isset($msgs[0]['Date']) || $msgs[0]['Date'] == '' || is_null($msgs[0]['Date'])) && $attempts == 0) {
$old_post = $post;
if ($type == 'newest') {
$res = $db->queryOneRow('SELECT p.number AS number FROM' . $groupa['bname'] . ' b, ' . $groupa['pname'] . ' p WHERE b.ID = b.releaseID AND b.ID = p.binaryID AND b.groupID = ' . $groupID . ' ORDER BY p.number DESC LIMIT 1');
if (isset($res['number']) && is_numeric($res['number'])) {
$post = $res['number'];
$dmessage =
"Unable to fetch article $old_post from " .
str_replace('alt.binaries', 'a.b', $group) .
". Retrying with newest article, from parts table, [$post] from ${groupa['pname']}";
$this->c->info($dmessage);
}
} else {
$res = $db->queryOneRow('SELECT p.number FROM ' . $groupa['bname'] . ' b, ' . $groupa['pname'] . ' p WHERE b.ID = p.binaryID AND b.groupID = ' . $groupID . ' ORDER BY p.number ASC LIMIT 1');
if (isset($res['number']) && is_numeric($res['number'])) {
$post = $res['number'];
$dmessage =
"Unable to fetch article $old_post from " .
str_replace('alt.binaries', 'a.b', $group) .
". Retrying with oldest article, from parts table, [$post] from ${groupa['pname']}.";
$this->c->info($dmessage);
}
}
$success = false;
}
if ((!isset($msgs[0]['Date']) || $msgs[0]['Date'] == '' || is_null($msgs[0]['Date'])) && $attempts != 0) {
if ($type == 'newest') {
$res = $db->queryOneRow('SELECT date FROM ' . $groupa['bname'] . ' ORDER BY date DESC LIMIT 1');
if (isset($res['date'])) {
$date = $res['date'];
$dmessage =
"Unable to fetch article $post from " .
str_replace('alt.binaries', 'a.b', $group) .
". Using newest date from ${groupa['bname']}.";
$this->c->info($dmessage);
if (strlen($date) > 0) {
$success = true;
}
}
} else {
$res = $db->queryOneRow('SELECT date FROM ' . $groupa['bname'] . ' ORDER BY date ASC LIMIT 1');
if (isset($res['date'])) {
$date = $res['date'];
$dmessage =
"Unable to fetch article $post from " .
str_replace('alt.binaries', 'a.b', $group) .
". Using oldest date from ${groupa['bname']}.";
$this->c->info($dmessage);
if (strlen($date) > 0) {
$success = true;
}
}
}
}
if (isset($msgs[0]['Date']) && $msgs[0]['Date'] != '' && $success === false) {
$date = $msgs[0]['Date'];
if (strlen($date) > 0) {
$success = true;
}
}
if ($attempts > 0) {
$this->c->debug('Retried ' . $attempts . " time(s).");
}
}
} while ($attempts <= 20 && $success === false);
if ($success === false && $old === true) {
if ($type == 'oldest') {
$res = $db->queryOneRow(sprintf("SELECT first_record_postdate from groups where name = '%s'", $group));
if (array_key_exists('first_record_postdate', $res)) {
$dmessage =
'Unable to fetch article ' .
$keeppost . ' from ' .
str_replace('alt.binaries', 'a.b', $group) .
'. Using current first_record_postdate[' .
$res['first_record_postdate'] .
"], instead.";
$this->c->info($dmessage);
return strtotime($res['first_record_postdate']);
} else {
return false;
}
} else {
$res = $db->queryOneRow(sprintf("SELECT last_record_postdate from groups where name = '%s'", $group));
if (array_key_exists('last_record_postdate', $res)) {
$dmessage =
'Unable to fetch article ' .
$keeppost . ' from ' .
str_replace('alt.binaries', 'a.b', $group) .
'. Using current last_record_postdate[' .
$res['last_record_postdate'] .
"], instead.";
$this->c->info($dmessage);
return strtotime($res['last_record_postdate']);
} else {
return false;
}
}
} else if ($success === false) {
return false;
}
$date = strtotime($date);
return $date;
}
/**
* Backfill groups using user specified article count.
*
* @param object $nntp
* @param string $groupName
* @param string $articles
* @param string $type
*
* @return void
*/
public
function backfillPostAllGroups($nntp, $groupName = '', $articles = '', $type = '')
{
if (!isset($nntp)) {
$dmessage = "Not connected to usenet(backfill->backfillPostAllGroups).\n";
exit($this->c->error($dmessage));
}
$res = false;
$groups = new Groups();
if ($groupName != '') {
$grp = $groups->getByName($groupName);
if ($grp) {
$res = array($grp);
}
} else {
if ($type == 'normal') {
$res = $this->getActiveBackfill();
} else if ($type == 'date') {
$res = $this->getActiveByDateBackfill();
}
}
if ($res) {
$counter = 1;
$binaries = new Binaries();
foreach ($res as $groupArr) {
if ($groupName === '') {
$dmessage = "\nStarting group " . $counter . ' of ' . sizeof($res);
$this->c->header . $dmessage . $this->c->rsetColor();
}
$this->backfillPostGroup($nntp, $this->db, $binaries, $groupArr, sizeof($res) - $counter, $articles);
$counter++;
}
} else {
$dmessage = "No groups specified. Ensure groups are added to newznab's database for updating.";
$this->c->warning($dmessage);
}
}
/**
* @param string $group
* @param int $first
* @param int $last
* @param int $threads
* @param object $nntp
*
* @return void
*/
public
function getRange($group, $first, $last, $threads, $nntp)
{
if (!isset($nntp)) {
exit($this->c->error("Not connected to usenet(backfill->getRange).\n"));
}
$groups = new Groups();
$this->startGroup = microtime(true);
$binaries = new Binaries();
$groupArr = $groups->getByName($group);
$process = $this->safepartrepair ? 'update' : 'backfill';
$this->c->header(
'Processing ' .
str_replace('alt.binaries', 'a.b', $groupArr['name']) .
(($this->compressedHeaders) ? ' Using Compression' : ' Not Using Compression') .
' ==> T-' .
$threads .
' ==> ' .
number_format($first) .
' to ' .
number_format($last) .
$this->c->rsetColor()
);
$this->startLoop = microtime(true);
// Select group, here, only once
$data = $nntp->selectGroup($groupArr['name']);
if ($nntp->isError($data)) {
$data = $nntp->dataError($nntp, $groupArr['name']);
if ($nntp->isError($data)) {
return;
}
}
$binaries->scan($nntp, $groupArr, $last, $first, $process);
}
/**
* Backfill all the groups up to user specified time/date.
*
* @param object $nntp
* @param string $groupName
*
* @return void
*/
public
function backfillAllGroups($nntp, $groupName = '')
{
if (!isset($nntp)) {
$dmessage = "Not connected to usenet(backfill->backfillAllGroups).\n";
exit($this->c->error($dmessage));
}
$groups = new Groups();
if ($groupName != '') {
$grp = $groups->getByName($groupName);
if ($grp) {
$res = array($grp);
}
} else {
$res = $this->getActiveBackfill();
}
if ($res) {
$counter = 1;
$db = $this->db;
$binaries = new Binaries();
foreach ($res as $groupArr) {
if ($groupName === '') {
$dmessage = "Starting group " . $counter . ' of ' . sizeof($res);
$this->c->header . $dmessage . $this->c->rsetColor();
}
$this->backfillGroup($nntp, $db, $binaries, $groupArr, sizeof($res) - $counter);
$counter++;
}
} else {
$dmessage = "No groups specified. Ensure groups are added to newznab's database for updating.";
$this->c->primary . $dmessage . $this->c->rsetColor();
}
}
public
function backfillGroup($nntp, $db, $binaries, $groupArr, $left)
{
if (!isset($nntp)) {
$dmessage = "Not connected to usenet(backfill->backfillGroup).";
exit($this->c->error($dmessage));
}
$this->startGroup = microtime(true);
// Select group, here, only once
$data = $nntp->selectGroup($groupArr['name']);
if ($nntp->isError($data)) {
$data = $nntp->dataError($nntp, $groupArr['name']);
if ($nntp->isError($data)) {
return;
}
}
// Get targetpost based on days target.
$targetpost = $this->daytopost($nntp, $groupArr['name'], $groupArr['backfill_target'], $data, true);
if ($targetpost < 0) {
$targetpost = round($data['first']);
}
if ($groupArr['first_record'] == 0 || $groupArr['backfill_target'] == 0) {
$dmessage = "Group ${groupArr['name']} has invalid numbers. Have you run update on it? Have you set the backfill days amount?";
$this->c->warning($dmessage);
return;
}
// Check if we are grabbing further than the server has.
if ($groupArr['first_record'] <= ($data['first'] + 50000)) {
$dmessage = "We have hit the maximum we can backfill for " . str_replace('alt.binaries', 'a.b', $groupArr['name']) . ", skipping it.";
$this->c->notice($dmessage);
//$groups = new Groups();
//$groups->disableForPost($groupArr['name']);
return;
}
// If our estimate comes back with stuff we already have, finish.
if ($targetpost >= $groupArr['first_record']) {
$dmessage = "Nothing to do, we already have the target post.";
$this->c->notice($dmessage);
return;
}
$this->c->doecho(
'Group ' .
$data['group'] .
': server has ' .
number_format($data['first']) .
' - ' .
number_format($data['last']) .
', or ~' .
((int)
((
$this->postdate($nntp, $data['last'], false, $groupArr['name'], false, 'oldest') -
$this->postdate($nntp, $data['first'], false, $groupArr['name'], false, 'oldest')) /
86400
)) .
" days.\nLocal first = " .
number_format($groupArr['first_record']) .
' (' .
((int)
((
date('U') -
$this->postdate($nntp, $groupArr['first_record'], false, $groupArr['name'], false, 'oldest')) /
86400
)) .
' days). Backfill target of ' .
$groupArr['backfill_target'] .
' days is post ' .
$targetpost, true
);
$done = false;
// Set first and last, moving the window by maxxMssgs.
$last = $groupArr['first_record'] - 1;
// Set the initial "chunk".
$first = $last - $this->messagebuffer + 1;
// Just in case this is the last chunk we needed.
if ($targetpost > $first) {
$first = $targetpost;
}
// In case postdate doesn't get a date.
if (is_null($groupArr['first_record_postdate']) || $groupArr['first_record_postdate'] == 'NULL') {
$firstr_date = time();
} else {
$firstr_date = strtotime($groupArr['first_record_postdate']);
}
while ($done === false) {
$this->startLoop = microtime(true);
$this->c->header(
'Getting ' .
(number_format($last - $first + 1)) .
" articles from " .
str_replace('alt.binaries', 'a.b', $data['group']) .
", " . $left .
" group(s) left. (" .
(number_format($first - $targetpost)) .
" articles in queue)." .
$this->c->rsetColor()
);
flush();
$process = $this->safepartrepair ? 'update' : 'backfill';
$binaries->scan($nntp, $groupArr, $first, $last, $process);
$newdate = $this->postdate($nntp, $first, false, $groupArr['name'], true, 'oldest');
if ($newdate !== false) {
$firstr_date = $newdate;
}
$db->exec(sprintf('UPDATE groups SET first_record_postdate = %s, first_record = %s, last_updated = NOW() WHERE ID = %d', $this->from_unixtime($firstr_date), $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 - $this->messagebuffer + 1;
if ($targetpost > $first) {
$first = $targetpost;
}
}
}
// Set group's first postdate.
$db->exec(sprintf('UPDATE groups SET first_record_postdate = %s, last_updated = NOW() WHERE ID = %d', $this->from_unixtime($firstr_date), $groupArr['ID']));
$timeGroup = number_format(microtime(true) - $this->startGroup, 2);
$this->c->primary(
'Group processed in ' .
$timeGroup .
" seconds." .
$this->c->rsetColor()
);
}
/**
* Returns article number based on # of days.
*
* @param object $nntp
* @param string $group
* @param int $days
* @param array $data
* @param bool $debug
*
* @return string
*/
public
function daytopost($nntp, $group, $days, $data, $debug = true)
{
if (!isset($nntp)) {
$dmessage = "Not connected to usenet(backfill->daytopost).\n";
exit($this->c->error($dmessage));
}
$pddebug = false;
// Goal timestamp.
$goaldate = date('U') - (86400 * $days);
$totalnumberofarticles = $data['last'] - $data['first'];
$upperbound = $data['last'];
$lowerbound = $data['first'];
if ($data['last'] == PHP_INT_MAX) {
$dmessage = "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";
exit($this->c->info($dmessage));
}
$firstDate = $this->postdate($nntp, $data['first'], $pddebug, $group, false, 'oldest');
$lastDate = $this->postdate($nntp, $data['last'], $pddebug, $group, false, 'oldest');
if ($goaldate < $firstDate) {
$dmessage =
"Backfill target of $days day(s) is older than the first article stored on your news server.\nStarting from the first available article (" .
date('r', $firstDate) . ' or ' .
$this->daysOld($firstDate) . " days).";
$this->c->warning($dmessage);
return $data['first'];
} else if ($goaldate > $lastDate) {
$dmessage =
'Backfill target of ' .
$days .
" day(s) is newer than the last article stored on your news server.\nTo backfill this group you need to set Backfill Days to at least " .
ceil($this->daysOld($lastDate) + 1) .
' days (' .
date('r', $lastDate - 86400) .
").";
$this->c->error($dmessage);
return '';
}
$interval = floor(($upperbound - $lowerbound) * 0.5);
$templowered = '';
$dateofnextone = $lastDate;
// Match on days not timestamp to speed things up.
while ($this->daysOld($dateofnextone) < $days) {
while (($tmpDate = $this->postdate($nntp, ($upperbound - $interval), $pddebug, $group, false, 'oldest')) > $goaldate) {
$upperbound = $upperbound - $interval;
}
if (!$templowered) {
$interval = ceil(($interval / 2));
}
$dateofnextone = $this->postdate($nntp, ($upperbound - 1), $pddebug, $group, false, 'oldest');
while (!$dateofnextone) {
$dateofnextone = $this->postdate($nntp, ($upperbound - 1), $pddebug, $group, false, 'oldest');
}
}
$dmessage =
'Determined to be article: ' .
number_format($upperbound) .
' which is ' .
$this->daysOld($dateofnextone) .
' days old (' .
date('r', $dateofnextone) .
')';
$this->c->doecho($dmessage, true);
return $upperbound;
}
/**
* 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);
}
/**
* 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 object $nntp
* @param string $articles
*
* @return void
*/
public
function safeBackfill($nntp, $articles = '')
{
if (!isset($nntp)) {
$dmessage = "Not connected to usenet(backfill->safeBackfill).\n";
exit($this->c->error($dmessage));
}
$db = $this->db;
$groupname = $db->queryOneRow(sprintf('SELECT name FROM groups WHERE first_record_postdate BETWEEN %s AND NOW() AND backfill = 1 ORDER BY name ASC', $db->escapeString($this->safebdate)));
if (!$groupname) {
$dmessage =
'No groups to backfill, they are all at the target date ' .
$this->safebdate .
", or you have not enabled them to be backfilled in the groups page.\n";
exit($dmessage);
} else {
$this->backfillPostAllGroups($nntp, $groupname['name'], $articles, $type = '');
}
}
/**
* @param object $nntp
* @param object $db
* @param object $binaries
* @param array $groupArr
* @param int $left
* @param string $articles
*
* @return void
*/
public
function backfillPostGroup($nntp, $db, $binaries, $groupArr, $left, $articles = '')
{
if (!isset($nntp)) {
$dmessage = "Not connected to usenet(backfill->backfillPostGroup).\n";
exit($this->c->error($dmessage));
}
$this->startGroup = microtime(true);
$this->c->header(
'Processing ' .
$groupArr['name'] .
$this->c->rsetColor()
);
// Select group, here, only once
$data = $nntp->selectGroup($groupArr['name']);
if ($nntp->isError($data)) {
$data = $nntp->dataError($nntp, $groupArr['name']);
if ($nntp->isError($data)) {
return;
}
}
// Get targetpost based on days target.
$targetpost = round($groupArr['first_record'] - $articles);
if ($targetpost < 0) {
$targetpost = round($data['first']);
}
if ($groupArr['first_record'] <= 0 || $targetpost <= 0) {
$dmessage =
"You need to run update_binaries on " .
str_replace('alt.binaries', 'a.b', $data['group']) .
". Otherwise the group is dead, you must disable it.";
$this->c->error($dmessage);
return;
}
// Check if we are grabbing further than the server has.
if ($groupArr['first_record'] <= $data['first'] + $articles) {
$dmessage =
"We have hit the maximum we can backfill for " .
str_replace('alt.binaries', 'a.b', $groupArr['name']) .
", skipping it.";
$this->c->notice($dmessage);
//$groups = new Groups();
//$groups->disableForPost($groupArr['name']);
return;
}
// If our estimate comes back with stuff we already have, finish.
if ($targetpost >= $groupArr['first_record']) {
$dmessage = "Nothing to do, we already have the target post.";
$this->c->notice($dmessage);
return;
}
$this->c->primary(
'Group ' . $data['group'] .
"'s oldest article is " .
number_format($data['first']) .
', newest is ' .
number_format($data['last']) .
'. The groups retention is: ' .
((int)
((
$this->postdate($nntp, $data['last'], false, $groupArr['name'], false, 'oldest') -
$this->postdate($nntp, $data['first'], false, $groupArr['name'], false, 'oldest')) /
86400
)) .
" days.\nOur oldest article is: " .
number_format($groupArr['first_record']) .
' which is (' .
((int)
((
date('U') -
$this->postdate($nntp, $groupArr['first_record'], false, $groupArr['name'], false, 'oldest')) /
86400
)) .
' days old). Our backfill target is article ' .
number_format($targetpost) .
' which is (' .
((int)
((
date('U') -
$this->postdate($nntp, $targetpost, false, $groupArr['name'], false, 'oldest')) /
86400
)) .
"\n days old)." .
$this->c->rsetColor()
);
// Calculate total number of parts.
$done = false;
// Set first and last, moving the window by maxxMssgs.
$last = $groupArr['first_record'] - 1;
// Set the initial "chunk".
$first = $last - $this->messagebuffer + 1;
// Just in case this is the last chunk we needed.
if ($targetpost > $first) {
$first = $targetpost;
}
// In case postdate doesn't get a date.
if (is_null($groupArr['first_record_postdate']) || $groupArr['first_record_postdate'] == 'NULL') {
$firstr_date = time();
} else {
$firstr_date = strtotime($groupArr['first_record_postdate']);
}
while ($done === false) {
$this->startLoop = microtime(true);
$this->c->header(
"\nGetting " .
($last - $first + 1) .
" articles from " .
str_replace('alt.binaries', 'a.b', str_replace('alt.binaries', 'a.b', $data['group'])) .
", " .
$left .
" group(s) left. (" .
(number_format($first - $targetpost)) .
" articles in queue)" .
$this->c->rsetColor()
);
flush();
$process = $this->safepartrepair ? 'update' : 'backfill';
$binaries->scan($nntp, $groupArr, $first, $last, $process);
$newdate = $this->postdate($nntp, $first, false, $groupArr['name'], true, 'oldest');
if ($newdate !== false) {
$firstr_date = $newdate;
}
$db->exec(sprintf('UPDATE groups SET first_record_postdate = %s, first_record = %s, last_updated = NOW() WHERE ID = %d', $this->from_unixtime($firstr_date), $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 - $this->messagebuffer + 1;
if ($targetpost > $first) {
$first = $targetpost;
}
}
}
// Set group's first postdate.
$db->exec(sprintf('UPDATE groups SET first_record_postdate = %s, last_updated = NOW() WHERE ID = %d', $this->from_unixtime($firstr_date), $groupArr['ID']));
$timeGroup = number_format(microtime(true) - $this->startGroup, 2);
$this->c->header(
$data['group'] .
' processed in ' .
$timeGroup .
" seconds." .
$this->c->rsetColor()
);
}
/**
* Download new headers for a single group.
*
* @param array $groupArr Array of MySQL results for a single group.
* @param object $nntp Instance of class NNTP
*
* @return void
*/
public
function updateGroup($groupArr, $nntp)
{
if (!isset($nntp)) {
$message = "Not connected to usenet(binaries->updateGroup).";
exit($this->c->error($message));
}
$this->startGroup = microtime(true);
$this->c->primary('Processing ' . str_replace('alt.binaries', 'a.b', $groupArr['name']));
$binaries = new Binaries();
// Select the group, here, needed for processing the group
$data = $nntp->selectGroup($groupArr['name']);
if ($nntp->isError($data)) {
$data = $nntp->dataError($nntp, $groupArr['name']);
if ($nntp->isError($data)) {
return;
}
}
// Attempt to repair any missing parts before grabbing new ones.
if ($groupArr['last_record'] != 0) {
if ($this->DoPartRepair) {
$this->c->primary("Part repair enabled. Checking for missing parts.");
$this->partRepair($nntp, $groupArr);
} else {
$this->c->primary("Part repair disabled by user.");
}
}
// Get first and last part numbers from newsgroup.
$db = $this->db;
if ($groupArr['last_record'] == 0) {
// For new newsgroups - determine here how far you want to go back.
if ($this->NewGroupScanByDays) {
$first = $this->daytopost($this->NewGroupDaysToScan, $data);
if ($first == '') {
$this->c->warning("Skipping group: {$groupArr['name']}");
return;
}
} else {
if ($data['first'] > ($data['last'] - ($this->NewGroupMsgsToScan + $this->messagebuffer))) {
$first = $data['first'];
} else {
$first = $data['last'] - ($this->NewGroupMsgsToScan + $this->messagebuffer);
}
}
$left = $this->messagebuffer;
$last = $grouplast = $data['last'] - $left;
} else {
$first = $groupArr['last_record'];
// Leave 50%+ of the new articles on the server for next run (allow server enough time to actually make parts available).
$newcount = $data['last'] - $first;
$left = 0;
if ($newcount > $this->messagebuffer) {
// Drop the remaining plus $this->messagebuffer, pick them up on next run
if ($newcount < (2 * $this->messagebuffer)) {
$left = ((int)($newcount / 2));
$last = $grouplast = ($data['last'] - $left);
} else {
$remainingcount = $newcount % $this->messagebuffer;
$left = $remainingcount + $this->messagebuffer;
$last = $grouplast = ($data['last'] - $left);
}
} else {
$left = ((int)($newcount / 2));
$last = $grouplast = ($data['last'] - $left);
}
}
// Generate postdate for first record, for those that upgraded.
if (is_null($groupArr['first_record_postdate']) && $groupArr['first_record'] != '0') {
$newdate = $this->postdate($groupArr['first_record'], $data);
if ($newdate !== false) {
$first_record_postdate = $newdate;
} else {
$first_record_postdate = time();
}
$groupArr['first_record_postdate'] = $first_record_postdate;
$db->exec(sprintf('UPDATE groups SET first_record_postdate = %s WHERE ID = %d', $this->from_unixtime($first_record_postdate), $groupArr['ID']));
}
// Defaults for post record first/last postdate
if (is_null($groupArr['first_record_postdate'])) {
$first_record_postdate = time();
} else {
$first_record_postdate = strtotime($groupArr['first_record_postdate']);
}
if (is_null($groupArr['last_record_postdate'])) {
$last_record_postdate = time();
} else {
$last_record_postdate = strtotime($groupArr['last_record_postdate']);
}
// Calculate total number of parts.
$total = $grouplast - $first;
$realtotal = $data['last'] - $first;
// If total is bigger than 0 it means we have new parts in the newsgroup.
if ($total > 0) {
if ($groupArr['last_record'] == 0) {
$this->c->primary(
'New group ' .
$data['group'] .
' starting with ' .
(($this->NewGroupScanByDays) ? $this->NewGroupDaysToScan
. ' days' : number_format($this->NewGroupMsgsToScan) .
' messages'
) .
" worth. Leaving " .
number_format($left) .
" for next pass.\nServer oldest: " .
number_format($data['first']) .
' Server newest: ' .
number_format($data['last']) .
' Local newest: ' .
number_format($groupArr['last_record'])
);
} else {
$this->c->primary(
'Group ' .
$data['group'] .
' has ' .
number_format($realtotal) .
" new articles. Leaving " .
number_format($left) .
" for next pass.\nServer oldest: " .
number_format($data['first']) . ' Server newest: ' .
number_format($data['last']) .
' Local newest: ' .
number_format($groupArr['last_record'])
);
}
$done = false;
// Get all the parts (in portions of $this->messagebuffer to not use too much memory).
while ($done === false) {
$this->startLoop = microtime(true);
if ($total > $this->messagebuffer) {
if ($first + $this->messagebuffer > $grouplast) {
$last = $grouplast;
} else {
$last = $first + $this->messagebuffer;
}
}
$first++;
$this->c->header(
"Getting " .
number_format($last - $first + 1) .
' articles (' . number_format($first) .
' to ' .
number_format($last) .
') from ' .
str_replace('alt.binaries', 'a.b', $data['group']) .
" - (" .
number_format($grouplast - $last) .
" articles in queue)."
);
flush();
// Get article headers from newsgroup. Let scan deal with nntp connection, else compression fails after first grab
$scanSummary = $binaries->scan($nntp, $groupArr, $first, $last);
// Scan failed - skip group.
if ($scanSummary == false) {
return;
}
// If new group, update first record & postdate
if (is_null($groupArr['first_record_postdate']) && $groupArr['first_record'] == '0') {
$groupArr['first_record'] = $scanSummary['firstArticleNumber'];
if (isset($scanSummary['firstArticleDate'])) {
$first_record_postdate = strtotime($scanSummary['firstArticleDate']);
}
$groupArr['first_record_postdate'] = $first_record_postdate;
$db->exec(sprintf('UPDATE groups SET first_record = %s, first_record_postdate = %s WHERE ID = %d', $scanSummary['firstArticleNumber'], $this->from_unixtime($db->escapeString($first_record_postdate)), $groupArr['ID']));
}
if (isset($scanSummary['lastArticleDate'])) {
$last_record_postdate = strtotime($scanSummary['lastArticleDate']);
}
$db->exec(sprintf('UPDATE groups SET last_record = %s, last_record_postdate = %s, last_updated = NOW() WHERE ID = %d', $db->escapeString($scanSummary['lastArticleNumber']), $this->from_unixtime($last_record_postdate), $groupArr['ID']));
if ($last == $grouplast) {
$done = true;
} else {
$first = $last;
}
}
$timeGroup = number_format(microtime(true) - $this->startGroup, 2);
$this->c->primary($data['group'] . ' processed in ' . $timeGroup . " seconds.");
} else {
$this->c->primary(
'No new articles for ' .
$data['group'] .
' (first ' .
number_format($first) .
' last ' .
number_format($last) .
' grouplast ' .
number_format($groupArr['last_record']) .
' total ' . number_format($total) .
")\n" .
"Server oldest: " .
number_format($data['first']) .
' Server newest: ' .
number_format($data['last']) .
' Local newest: ' .
number_format($groupArr['last_record'])
);
}
}
/**
* Attempt to get missing headers.
*
* @param $nntp Instance of class NNTP.
* @param $groupArr The info for this group from mysql.
*
* @return void
*/
public
function partRepair($nntp, $groupArr)
{
if (!isset($nntp)) {
$dmessage = "Not connected to usenet(functions->partRepair).";
exit($this->c->error("Not connected to usenet(functions->partRepair)."));
}
// Get all parts in partrepair table.
$db = $this->db;
// Check that tables exist, create if they do not
$group['prname'] = 'partrepair';
$missingParts = $db->query(sprintf('SELECT * FROM ' . $group['prname'] . ' WHERE groupID = %d AND attempts < 5 ORDER BY numberID ASC LIMIT %d', $groupArr['ID'], $this->partrepairlimit));
$partsRepaired = $partsFailed = 0;
if (sizeof($missingParts) > 0) {
$this->consoleTools->overWritePrimary(
'Attempting to repair ' .
number_format(sizeof($missingParts)) .
" parts."
);
// Loop through each part to group into continuous ranges with a maximum range of messagebuffer/4.
$ranges = array();
$partlist = array();
$firstpart = $lastnum = $missingParts[0]['numberID'];
foreach ($missingParts as $part) {
if (($part['numberID'] - $firstpart) > ($this->messagebuffer / 4)) {
$ranges[] = array('partfrom' => $firstpart, 'partto' => $lastnum, 'partlist' => $partlist);
$firstpart = $part['numberID'];
$partlist = array();
}
$partlist[] = $part['numberID'];
$lastnum = $part['numberID'];
}
$ranges[] = array('partfrom' => $firstpart, 'partto' => $lastnum, 'partlist' => $partlist);
$num_attempted = 0;
// Download missing parts in ranges.
foreach ($ranges as $range) {
$this->startLoop = microtime(true);
$partfrom = $range['partfrom'];
$partto = $range['partto'];
$partlist = $range['partlist'];
$count = sizeof($range['partlist']);
$num_attempted += $count;
$this->consoleTools->overWritePrimary("Attempting repair: " . $this->consoleTools->percentString2($num_attempted - $count + 1, $num_attempted, sizeof($missingParts)) . ': ' . $partfrom . ' to ' . $partto);
// Get article from newsgroup.
$binaries = new Binaries();
$binaries->scan($nntp, $groupArr, $partfrom, $partto, 'update');
}
// Calculate parts repaired
$sql = sprintf('SELECT COUNT(ID) AS num FROM ' . $group['prname'] . ' WHERE groupID=%d AND numberID <= %d', $groupArr['ID'], $missingParts[sizeof($missingParts) - 1]['numberID']);
$result = $db->queryOneRow($sql);
if (isset($result['num'])) {
$partsRepaired = (sizeof($missingParts)) - $result['num'];
}
// Update attempts on remaining parts for active group
if (isset($missingParts[sizeof($missingParts) - 1]['ID'])) {
$sql = sprintf("UPDATE ${group['prname']} SET attempts=attempts+1 WHERE groupID=%d AND numberID <= %d", $groupArr['ID'], $missingParts[sizeof($missingParts) - 1]['numberID']);
$result = $db->exec($sql);
if ($result) {
$partsFailed = $result->rowCount();
}
}
$this->c->primary(
"\n" .
number_format($partsRepaired) .
" parts repaired."
);
}
// Remove articles that we cant fetch after 5 attempts.
$db->exec(sprintf('DELETE FROM ' . $group['prname'] . ' WHERE attempts >= 5 AND groupID = %d', $groupArr['ID']));
}
/**
* Use cURL To download a web page into a string.
*
* @param string $url The URL to download.
* @param string $method get/post
* @param string $postdata If using POST, post your POST data here.
* @param string $language Use alternate langauge in header.
* @param bool $debug Show debug info.
* @param string $userAgent User agent.
* @param string $cookie Cookie.
*
* @return bool|mixed
*/
function getUrl($url, $method = 'get', $postdata = '', $language = "", $debug = false, $userAgent = '', $cookie = '')
{
switch ($language) {
case 'fr':
case 'fr-fr':
$language = "fr-fr";
break;
case 'de':
case 'de-de':
$language = "de-de";
break;
case 'en':
$language = 'en';
break;
case '':
case 'en-us':
default:
$language = "en-us";
}
$header[] = "Accept-Language: " . $language;
$ch = curl_init();
$options = array(
CURLOPT_URL => $url,
CURLOPT_HTTPHEADER => $header,
CURLOPT_RETURNTRANSFER => 1,
CURLOPT_FOLLOWLOCATION => 1,
CURLOPT_TIMEOUT => 15,
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_SSL_VERIFYHOST => false,
);
curl_setopt_array($ch, $options);
if ($userAgent !== '') {
curl_setopt($ch, CURLOPT_USERAGENT, $userAgent);
}
if ($cookie !== '') {
curl_setopt($ch, CURLOPT_COOKIE, $cookie);
}
if ($method === 'post') {
$options = array(
CURLOPT_POST => 1,
CURLOPT_POSTFIELDS => $postdata
);
curl_setopt_array($ch, $options);
}
if ($debug) {
$options =
array(
CURLOPT_HEADER => true,
CURLINFO_HEADER_OUT => true,
CURLOPT_NOPROGRESS => false,
CURLOPT_VERBOSE => true
);
curl_setopt_array($ch, $options);
}
$buffer = curl_exec($ch);
$err = curl_errno($ch);
curl_close($ch);
if ($err !== 0) {
return false;
} else {
return $buffer;
}
}
/**
* Formats a 'like' string. ex.(LIKE '%chocolate%')
*
* @param string $str The string.
* @param bool $left Add a % to the left.
* @param bool $right Add a % to the right.
*
* @return string
*/
public
function likeString($str, $left = true, $right = true)
{
return (
(DB_TYPE === 'mysql' ? 'LIKE ' : 'ILIKE ') .
$this->db->escapeString(
($left ? '%' : '') .
$str .
($right ? '%' : '')
)
);
}
// Check if O/S is windows.
function isWindows()
{
return (strtolower(substr(php_uname('s'), 0, 3)) === 'win');
}
/**
* Run CLI command.
*
* @param string $command
* @param bool $debug
*
* @return array
*/
function runCmd($command, $debug = false)
{
$nl = PHP_EOL;
if (isWindows() && strpos(phpversion(), "5.2") !== false) {
$command = "\"" . $command . "\"";
}
if ($debug) {
echo '-Running Command: ' . $nl . ' ' . $command . $nl;
}
$output = array();
$status = 1;
@exec($command, $output, $status);
if ($debug) {
echo '-Command Output: ' . $nl . ' ' . implode($nl . ' ', $output) . $nl;
}
return $output;
}
// Convert obj to array.
function objectsIntoArray($arrObjData, $arrSkipIndices = array())
{
$arrData = array();
// If input is object, convert into array.
if (is_object($arrObjData)) {
$arrObjData = get_object_vars($arrObjData);
}
if (is_array($arrObjData)) {
foreach ($arrObjData as $index => $value) {
// Recursive call.
if (is_object($value) || is_array($value)) {
$value = objectsIntoArray($value, $arrSkipIndices);
}
if (in_array($index, $arrSkipIndices)) {
continue;
}
$arrData[$index] = $value;
}
}
return $arrData;
}
/**
* Convert bytes to kb/mb/gb/tb and return in human readable format.
*
* @param int $bytes
*
* @return string
*/
protected
function readableBytesString($bytes)
{
$kb = 1024;
$mb = $kb * $kb;
$gb = $kb * $mb;
$tb = $kb * $gb;
if ($bytes < $kb) {
return $bytes . 'B';
} else if ($bytes < ($mb)) {
return round($bytes / $kb, 1) . 'KB';
} else if ($bytes < $gb) {
return round($bytes / $mb, 1) . 'MB';
} else if ($bytes < $tb) {
return round($bytes / $gb, 1) . 'GB';
} else {
return round($bytes / $tb, 1) . 'TB';
}
}
/**
* Strips non-printing characters from a string.
*
* Operates directly on the text string, but also returns the result for situations requiring a
* return value (use in ternary, etc.)/
*
* @param $text String variable to strip.
*
* @return string The stripped variable.
*/
static public function stripNonPrintingChars(&$text)
{
$lowChars = [
"\x00", "\x01", "\x02", "\x03", "\x04", "\x05", "\x06", "\x07",
"\x08", "\x09", "\x0A", "\x0B", "\x0C", "\x0D", "\x0E", "\x0F",
"\x10", "\x11", "\x12", "\x13", "\x14", "\x15", "\x16", "\x17",
"\x18", "\x19", "\x1A", "\x1B", "\x1C", "\x1D", "\x1E", "\x1F",
];
$text = str_replace($lowChars, '', $text);
return $text;
}
/**
* Get human readable size string from bytes.
*
* @param int $bytes Bytes number to convert.
* @param int $precision How many floating point units to add.
*
* @return string
*/
function bytesToSizeString($bytes, $precision = 0)
{
if ($bytes == 0) {
return '0B';
}
$unit = array('B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB');
return round($bytes / pow(1024, ($i = floor(log($bytes, 1024)))), $precision) . $unit[(int)$i];
}
/**
* Removes the preceeding or proceeding portion of a string
* relative to the last occurrence of the specified character.
* The character selected may be retained or discarded.
*
* @param string $character the character to search for.
* @param string $string the string to search through.
* @param string $side determines whether text to the left or the right of the character is returned.
* Options are: left, or right.
* @param bool $keep_character determines whether or not to keep the character.
* Options are: true, or false.
*
* @return string
*/
static public function cutStringUsingLast($character, $string, $side, $keep_character = true)
{
$offset = ($keep_character ? 1 : 0);
$whole_length = strlen($string);
$right_length = (strlen(strrchr($string, $character)) - 1);
$left_length = ($whole_length - $right_length - 1);
switch ($side) {
case 'left':
$piece = substr($string, 0, ($left_length + $offset));
break;
case 'right':
$start = (0 - ($right_length + $offset));
$piece = substr($string, $start);
break;
default:
$piece = false;
break;
}
return ($piece);
}
/**
* Detect if the command is accessible on the system.
*
* @param $cmd
*
* @return bool|null Returns true if found, false if not found, and null if which is not detected.
*/
static public function hasCommand($cmd)
{
if (HAS_WHICH) {
$returnVal = shell_exec("which $cmd");
return (empty($returnVal) ? false : true);
} else {
return null;
}
}
//end of testing
}