time = (int)$options['INITIAL_TIME']; else $this->time = time(); $this->useLock = (bool)($options['USE_LOCK'] ?? null); $this->options = $options; $this->restore(); if (isset($options['STEP']) && $options['STEP'] == 0) { $this->reset(); } $this->logMessage('#############################', false); $this->logMessage('HIT STARTED '.$this->getTimeStampString(), false); $timeLimit = (int)($options['TIME_LIMIT'] ?? 0); if ($timeLimit > 0) { $this->setTimeLimit($timeLimit); } $this->saveStartTime(); $this->saveMemoryPeak(); } public function addStage($params) { if (empty($params['CODE']) || empty($params['CALLBACK'])) { throw new Main\SystemException('Not enought params to add stage'); } $ss = (int)($params['STEP_SIZE'] ?? 0); $order = count($this->stages); $type = (string)($params['TYPE'] ?? ''); if ($type === '') { $type = static::CALLBACK_TYPE_MANUAL; } $beforeCallback = (string)($params['ON_BEFORE_CALLBACK'] ?? ''); if ($beforeCallback === '') { $beforeCallback = false; } $afterCallback = (string)($params['ON_AFTER_CALLBACK'] ?? ''); if ($afterCallback === '') { $afterCallback = false; } $this->stages[] = [ 'STEP_SIZE' => $ss ?: 1, 'PERCENT' => (int)($params['PERCENT'] ?? 0), 'CODE' => $params['CODE'], 'ORDER' => $order, 'TYPE' => $type, 'CALLBACK' => $params['CALLBACK'], 'SUBPERCENT_CALLBACK' => $params['SUBPERCENT_CALLBACK'] ?? null, 'ON_BEFORE_CALLBACK' => $beforeCallback, 'ON_AFTER_CALLBACK' => $afterCallback, ]; $this->stagesByCode[$params['CODE']] =& $this->stages[count($this->stages) - 1]; } public function restore() { if(!isset($_SESSION[$this->sessionKey]['STAGE'])) $_SESSION[$this->sessionKey]['STAGE'] = 0; if(!isset($_SESSION[$this->sessionKey]['STEP'])) $_SESSION[$this->sessionKey]['STEP'] = 0; if(!isset($_SESSION[$this->sessionKey]['DATA'])) $_SESSION[$this->sessionKey]['DATA'] = array(); $this->stage =& $_SESSION[$this->sessionKey]['STAGE']; $this->step =& $_SESSION[$this->sessionKey]['STEP']; $this->data =& $_SESSION[$this->sessionKey]['DATA']; } // reset current condition public function reset() { $this->stage = 0; $this->step = 0; $this->data = array(); $this->clearLogFile(); $this->saveStartTime(); $this->saveMemoryPeak(); } public function performStage() { return $this->performIteration(); } public function performIteration() { if($this->stage == 0 && $this->step == 0) { $this->lockProcess(); if(static::DEBUG_MODE) { $logDir = $this->getLogFileDir(); if(!file_exists($logDir)) mkdir($logDir, 755, true); $this->logMessage('PROCESS STARTED, STAGE '.$this->stages[0]['CODE']); } } $this->onBeforePerformIteration(); if(!isset($this->stages[$this->stage])) throw new Main\SystemException('No more stages to perform'); if(self::JUST_SHOW_STAGES) $this->nextStage(); else { $stage = $this->stage; if($this->stages[$stage]['ON_BEFORE_CALLBACK'] != false) call_user_func(array($this, $this->stages[$stage]['ON_BEFORE_CALLBACK'])); if($this->stages[$this->stage]['TYPE'] == static::CALLBACK_TYPE_MANUAL) call_user_func(array($this, $this->stages[$this->stage]['CALLBACK'])); elseif($this->stages[$this->stage]['TYPE'] == static::CALLBACK_TYPE_QUOTA) { while($this->checkQuota()) { $result = call_user_func(array($this, $this->stages[$this->stage]['CALLBACK'])); $this->nextStep(); if($result) break; } if($result) $this->nextStage(); } if($this->stages[$stage]['ON_AFTER_CALLBACK'] != false) call_user_func(array($this, $this->stages[$stage]['ON_AFTER_CALLBACK'])); } $this->onAfterPerformIteration(); $percent = $this->getPercent(); $this->saveMemoryPeak(); $this->logMessage('HIT ENDED '.$this->getTimeStampString(), false); if($percent == 100) $this->unLockProcess(); return $percent; } ///////////////////////////////////////////////// /// Staging ///////////////////////////////////////////////// public function setStepSize($code, $stepSize) { if(!isset($this->stagesByCode[$code])) throw new Main\SystemException('Unknown stage code passed'); if(($stepSize = intval($stepSize)) <= 0) throw new Main\SystemException('Bad step size passed'); $this->stagesByCode[$code]['STEP_SIZE'] = $stepSize; } // move to next stage public function nextStage() { $this->stage++; $this->step = 0; if ($this->stage < count($this->stages)) { $stageCode = $this->stages[$this->stage]['CODE'] ?? 'UNDEFINED for '.$this->stage; } else { $stageCode = 'FINAL'; } $this->logMessage( '### NEXT STAGE >>> ' . $stageCode . ' in ' . $this->getElapsedTimeString() . ', mem peak = ' . $this->getMemoryPeakString() . ' mb' ); } // move to next step public function nextStep() { $this->step++; } public function isStage($code) { return $this->stages[$this->stage]['CODE'] == $code; } protected function stageCompare($code, $way) { $currIndex = $this->stages[$this->stage]['ORDER']; $stageIndex = $this->stagesByCode[$code]['ORDER']; if($currIndex == $stageIndex) return true; if($way) // gt return $currIndex > $stageIndex; else // lt return $currIndex < $stageIndex; } // $this->stage <= $code public function stageLT($code) { return $this->stageCompare($code, false); } // $code <= $this->stage public function stageGT($code) { return $this->stageCompare($code, true); } public function setStage($stage) { foreach($this->stages as $sId => $info) { if($info['CODE'] == $stage) { $this->stage = $sId; $this->step = 0; break; } } } public function onBeforePerformIteration() { } public function onAfterPerformIteration() { } public function getStageCode() { return $this->stages[$this->stage]['CODE'] ?? ''; } public function getCurrStageIndex() { return $this->stage; } public function getStep() { return $this->step; } public function getStage($code) { return $this->stagesByCode[$code]; } public function getCurrStageStepSize() { return $this->stages[$this->stage]['STEP_SIZE']; } ///////////////////////////////////////////////// /// Percentage ///////////////////////////////////////////////// public function getStagePercent($sNum = false) { if ($sNum === false) { $stage = $this->stages[$this->stage]['PERCENT'] ?? 0; } else { if (is_numeric($sNum)) { $stage = $this->stages[$sNum]['PERCENT'] ?? 0; } else { $stage = $this->stagesByCode[$sNum]['PERCENT'] ?? 0; } } return $stage ?: 0; } public function getPercentBetween($codeFrom, $codeTo) { return $this->getStagePercent($codeTo) - $this->getStagePercent($codeFrom); } public function getPercentFromToCurrent($codeFrom){ return $this->getStagePercent($this->stage - 1) - $this->getStagePercent($codeFrom); } public function getCurrentPercentRange() { return $this->getStagePercent($this->stage) - $this->getStagePercent($this->stage - 1); } public function getPercent() { $percent = $this->stage > 0 ? $this->stages[$this->stage - 1]['PERCENT'] : 0; $addit = 0; $cb = (string)($this->stages[$this->stage]['SUBPERCENT_CALLBACK'] ?? ''); if ($cb !== '' && method_exists($this, $cb)) { $addit = $this->$cb(); } return $percent + $addit; } public function calcSubPercent($range) { if(!$range) return 0; return round(($this->step / $range)*($this->getStagePercent($this->stage) - $this->getStagePercent($this->stage - 1))); } public function getSubPercentByTotalAndDone($total, $done = 0) { if(!$done || !$total) return 0; $pRange = $this->getCurrentPercentRange(); $part = round($pRange * ($done / $total)); return $part >= $pRange ? $pRange : $part; } ///////////////////////////////////////////////// /// Quotas info ///////////////////////////////////////////////// public function checkQuota() { return (time() - $this->time) < $this->timeLimit; } public function setTimeLimit($timeLimit) { $timeLimit = (int)$timeLimit; if ($timeLimit > 0) { $this->timeLimit = max($timeLimit, static::MIN_TIME_LIMIT); } } public function getMemoryPeak() { return $this->data['memory_peak']; } protected function saveStartTime() { if (!isset($this->data['process_time'])) { $this->data['process_time'] = time(); } } protected function saveMemoryPeak() { $mp = memory_get_peak_usage(false); if (!isset($this->data['memory_peak'])) { $this->data['memory_peak'] = $mp; } else { if ($this->data['memory_peak'] < $mp) { $this->data['memory_peak'] = $mp; } } } ///////////////////////////////////////////////// /// Logging ///////////////////////////////////////////////// public function clearLogFile() { $logDir = $this->getLogFileDir(); if(!Main\IO\Directory::isDirectoryExists($logDir)) Main\IO\Directory::createDirectory($logDir); $logFile = $this->getLogFilePath(); Main\IO\File::putFileContents($logFile, ''); } public function getLogFileDir() { return $_SERVER['DOCUMENT_ROOT'].'/'.str_replace('%BX_ROOT%', BX_ROOT, self::DEBUG_FOLDER); } public function getLogFilePath() { return $this->getLogFileDir().str_replace('%SESSION_KEY%', $this->sessionKey, self::DEBUG_FILE); } public function logMessage($message = '', $addTimeStamp = true) { if(!static::DEBUG_MODE || !mb_strlen($message)) return; file_put_contents( $this->getLogFilePath(), ($addTimeStamp ? $this->getTimeStampString().' ' : '').$message.PHP_EOL, FILE_APPEND ); } public function logMemoryUsage() { $this->logMessage('MEMORY USAGE: '.(memory_get_usage(false) / (1024 * 1024)).' MB', false); } public function logFinalResult() { $this->logMessage('ALL DONE!'); $this->logMessage('TOTAL PROCESS TIME: '.$this->getElapsedTimeString(), false); $this->logMessage('MEMORY PEAK (mb): '.$this->getMemoryPeakString(), false); } ///////////////////////////////////////////////// /// Lock ///////////////////////////////////////////////// public function getLockFilePath() { return $this->getLogFileDir().str_replace('%SESSION_KEY%', $this->sessionKey, self::LOCK_FILE); } public function lockProcess() { if(!$this->useLock) return; file_put_contents($this->getLockFilePath(), '1'); } public function unLockProcess() { if(!$this->useLock) return; $file = $this->getLockFilePath(); if(file_exists($file)) unlink($file); } public function checkProcessLocked() { return $this->useLock && file_exists($this->getLockFilePath()); } ///////////////////////////////////////////////// /// Diagnostics tools ///////////////////////////////////////////////// protected function getHitTime() { return time() - $this->time; } protected function getProcessTime() { return time() - $this->data['process_time']; } protected function getProcessTimeString() { return $this->getTimeString($this->getProcessTime()); } protected function getHitTimeString() { return $this->getTimeString($this->getHitTime()); } protected function getElapsedTimeString() { return $this->getProcessTimeString(); } protected function getTimeString($time = 0) { $h = floor($time / 3600); $m = floor(($time - $h * 3600) / 60); $s = $time - $h * 3600 - $m * 60; if(mb_strlen($m) == 1) $m = '0'.$m; if(mb_strlen($s) == 1) $s = '0'.$s; return $h.':'.$m.':'.$s; } protected function getTimeStampString() { return '['.date('H:i:s').']'; } protected function getMemoryPeakString() { return $this->getMemoryPeak() / 1048576; } ///////////////////////////////////////////////// /// Util ///////////////////////////////////////////////// public function getData() { return $this->data; } // special case for array protected function getBlock($from) { $step = $this->step; for($i = 0; $i < $step; $i++) next($from); $block = array(); $hadSmth = false; for($i = $step; $i <= $step + $this->getCurrStageStepSize(); $i++) { $code = key($from); if(!isset($code)) { break; } $elem = current($from); next($from); $hadSmth = true; $block[$code] = $elem; $this->nextStep(); } if(!$hadSmth) { $this->nextStage(); return false; } return $block; } }