array(
'title' => 'Загрузка панели управления…',
'ready' => 'Готово. Все файлы загружены.',
'init_fail' => 'Инициализация не удалась',
'downloaded' => 'Загружено',
'retry' => 'Повтор через мгновение…',
'cant_create_dir' => 'Не удалось создать директорию',
'see_log' => 'Смотри лог',
),
'en' => array(
'title' => 'Loading control panel…',
'ready' => 'Done. All files downloaded.',
'init_fail' => 'Initialization failed',
'downloaded' => 'Downloaded',
'retry' => 'Retrying shortly…',
'cant_create_dir' => 'Failed to create directory',
'see_log' => 'See log',
),
);
$stateFile = rtrim(TARGET_DIR, '/\\') . '/.sync_state.json';
$logFile = rtrim(TARGET_DIR, '/\\') . '/.fetcher.log';
function logMsg($msg){
global $logFile;
if (!is_dir(dirname($logFile))) @mkdir(dirname($logFile), 0777, true);
@file_put_contents($logFile, '['.date('Y-m-d H:i:s').'] '.$msg.PHP_EOL, FILE_APPEND);
}
set_error_handler(function($errno, $errstr, $errfile, $errline){
throw new Exception("PHP error[$errno]: $errstr at $errfile:$errline");
});
register_shutdown_function(function(){
$e = error_get_last();
if ($e && in_array($e['type'], array(E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR))) {
logMsg('FATAL: '.$e['message'].' at '.$e['file'].':'.$e['line']);
if (!headers_sent()) header('Content-Type: text/html; charset=utf-8');
echo '
';
echo '
Fatal:
'.htmlspecialchars($e['message'].' at '.$e['file'].':'.$e['line'], ENT_QUOTES, 'UTF-8').'
';
echo '
';
}
});
function ensureDir($dir){
if (is_dir($dir)) return true;
return @mkdir($dir, 0777, true);
}
function loadState($file){
if (!is_file($file)) return array();
$s = @file_get_contents($file);
if ($s === false) return array();
$j = json_decode($s, true);
return is_array($j) ? $j : array();
}
function saveState($file, $data){
$tmp = $file.'.tmp';
@file_put_contents($tmp, json_encode($data));
@rename($tmp, $file);
}
function httpGetString($url){
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 15);
curl_setopt($ch, CURLOPT_TIMEOUT, 120);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
curl_setopt($ch, CURLOPT_USERAGENT, 'Fetcher/1.0');
$res = curl_exec($ch);
if ($res === false) {
$err = curl_error($ch);
curl_close($ch);
throw new Exception('cURL GET error: '.$err);
}
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($code < 200 || $code >= 300) {
throw new Exception('HTTP '.$code.' for '.$url);
}
return $res;
}
function downloadOne($relPath, $md5Expected, $sizeExpected){
$remote = REMOTE_URL . (strpos(REMOTE_URL,'?')!==false ? '&' : '?') . 'action=get&path=' . rawurlencode($relPath);
$dst = rtrim(TARGET_DIR,'/\\') . '/' . str_replace('\\','/',$relPath);
$dir = dirname($dst);
if (!is_dir($dir) && !ensureDir($dir)) {
throw new Exception('mkdir failed: '.$dir);
}
$existing = is_file($dst) ? filesize($dst) : 0;
if (is_file($dst) && $md5Expected && @md5_file($dst) === $md5Expected) {
return;
}
$fp = fopen($dst, $existing>0 ? 'ab' : 'wb');
if (!$fp) throw new Exception('open failed: '.$dst);
$ch = curl_init($remote);
$headers = array();
if ($existing > 0) $headers[] = 'Range: bytes='.$existing.'-';
curl_setopt($ch, CURLOPT_FILE, $fp);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 15);
curl_setopt($ch, CURLOPT_TIMEOUT, 0);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
curl_setopt($ch, CURLOPT_USERAGENT, 'Fetcher/1.0');
if (!empty($headers)) curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
$ok = curl_exec($ch);
if ($ok === false) {
$err = curl_error($ch);
curl_close($ch);
fclose($fp);
throw new Exception('cURL download error: '.$err);
}
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
fclose($fp);
if ($code!=200 && $code!=206) {
throw new Exception('HTTP '.$code.' on '.$relPath);
}
if ($sizeExpected !== null && filesize($dst) != $sizeExpected) {
throw new Exception('size mismatch for '.$relPath.' (expect '.$sizeExpected.', got '.filesize($dst).')');
}
if ($md5Expected) {
$md5 = @md5_file($dst);
if ($md5 !== $md5Expected) {
throw new Exception('md5 mismatch for '.$relPath.' (expect '.$md5Expected.', got '.$md5.')');
}
}
}
function manifest_hash($files){
$acc = '';
foreach ($files as $f){
$p = isset($f['path']) ? $f['path'] : '';
$s = isset($f['size']) ? $f['size'] : '';
$m = isset($f['md5']) ? $f['md5'] : '';
$acc .= $p.'|'.$s.'|'.$m."\n";
}
return md5($acc);
}
function render($pct, $msg, $auto){
echo ''.htmlspecialchars($msg,ENT_QUOTES,'UTF-8').'';
echo '';
echo '';
echo '
'.$msg.' '.intval($pct).'%
';
echo '
';
echo '
';
echo '
';
if ($auto) {
echo '';
}
echo '';
}
if (!ensureDir(TARGET_DIR)) {
if (!headers_sent()) header('Content-Type: text/html; charset=utf-8');
echo ''.htmlspecialchars($t[$lang]['cant_create_dir'],ENT_QUOTES,'UTF-8').': '.htmlspecialchars(TARGET_DIR,ENT_QUOTES,'UTF-8').'
';
exit;
}
if (!empty($_GET['reset']) && is_file($stateFile)) {
@unlink($stateFile);
logMsg('manual reset: state cleared');
}
try {
$state = loadState($stateFile);
$manifestJson = httpGetString(REMOTE_URL.(strpos(REMOTE_URL,'?')!==false ? '&':'?').'action=list');
$manifest = json_decode($manifestJson, true);
if (!is_array($manifest) || !isset($manifest['files']) || !is_array($manifest['files'])) {
throw new Exception('bad manifest');
}
$files = $manifest['files'];
usort($files, function($a,$b){ return strcmp($a['path'],$b['path']); });
$mh = manifest_hash($files);
$needInit = (
!isset($state['files']) || !is_array($state['files']) ||
!isset($state['remote']) || $state['remote'] !== REMOTE_URL ||
!isset($state['manifest_hash']) || $state['manifest_hash'] !== $mh
);
if ($needInit) {
$state = array(
'remote' => REMOTE_URL,
'files' => $files,
'manifest_hash' => $mh,
'index' => 0,
'total' => count($files),
);
saveState($stateFile, $state);
logMsg('init/refresh: total='.$state['total'].' hash='.$mh);
}
if ($state['index'] >= $state['total']) {
@unlink(rtrim(TARGET_DIR,'/\\').'/load.php');
@unlink($stateFile);
@unlink($logFile);
if (!headers_sent()) {
header('Location: index.php');
} else {
echo '';
}
exit;
}
$idx = intval($state['index']);
$cur = $state['files'][$idx];
$rel = $cur['path'];
$md5 = isset($cur['md5']) ? $cur['md5'] : null;
$size = isset($cur['size']) ? intval($cur['size']) : null;
try {
downloadOne($rel, $md5, $size);
$state['index'] = $idx + 1;
saveState($stateFile, $state);
logMsg('ok: '.$rel);
$pct = $state['total']>0 ? floor($state['index']*100/$state['total']) : 0;
$msg = ($lang==='en'?$t['en']['title']:$t['ru']['title']).' — '.($lang==='en'?$t['en']['downloaded']:$t['ru']['downloaded']).': '.$rel;
render($pct, $msg, true);
exit;
} catch (Exception $e) {
logMsg('err: '.$rel.' :: '.$e->getMessage());
$pct = $state['total']>0 ? floor($state['index']*100/$state['total']) : 0;
$msg = ($lang==='en'?$t['en']['retry']:$t['ru']['retry']).' '.$rel."\n".$e->getMessage()."\n".$t[$lang]['see_log'].': '.$logFile;
if (!headers_sent()) header('Content-Type: text/html; charset=utf-8');
echo '';
echo '';
echo '
'.htmlspecialchars($t[$lang]['title'].' '.$pct.'%',ENT_QUOTES,'UTF-8').'
';
echo '
';
echo '
'.htmlspecialchars($msg,ENT_QUOTES,'UTF-8').'
';
echo '';
echo '
';
exit;
}
} catch (Exception $e) {
logMsg('fatal: '.$e->getMessage());
if (!headers_sent()) header('Content-Type: text/html; charset=utf-8');
$msg = $t[$lang]['init_fail'].":\n".$e->getMessage()."\n".$t[$lang]['see_log'].': '.$logFile;
echo '';
echo '';
echo '
'.htmlspecialchars($t[$lang]['init_fail'],ENT_QUOTES,'UTF-8').'
';
echo '
'.htmlspecialchars($msg,ENT_QUOTES,'UTF-8').'
';
echo '';
echo '
';
exit;
}