little more debug info in updater extension

This commit is contained in:
maxpozdeev 2023-04-06 12:36:06 +03:00
parent 4c01eca4ad
commit 28c59c3c5b
6 changed files with 171 additions and 105 deletions

View file

@ -2,7 +2,7 @@
/*
This file is a part of myTinyTodo.
(C) Copyright 2022 Max Pozdeev <maxpozdeev@gmail.com>
(C) Copyright 2022-2023 Max Pozdeev <maxpozdeev@gmail.com>
Licensed under the GNU GPL version 2 or any later. See file COPYRIGHT for details.
*/
@ -16,14 +16,22 @@ class Controller extends \ApiController
function postCheck()
{
$prefs = UpdaterExtension::preferences();
$a = UpdaterExtension::lastVersionInfo();
$updater = new Updater;
$a = $updater->lastVersionInfo();
if ($a) {
$prefs['lastCheck'] = time();
$prefs['version'] = $a['version'] ?? '';
$prefs['download'] = $a['download'] ?? '';
Config::saveDomain(UpdaterExtension::domain, $prefs);
$this->response->data = [ 'total' => 1 ];
}
else {
$this->response->data = [
'total' => 0,
'msg' => __("error"),
'details' => $updater->lastErrorString ?? ''
];
}
$this->response->data = [ 'total' => 1 ];
}
function postUpdate()
@ -34,15 +42,22 @@ class Controller extends \ApiController
$this->response->data = [ 'total' => 0, 'msg' => __("updater.download_error") ];
return;
}
$updater = new Updater;
$file = MTTPATH. 'update.tar.gz';
$error = null;
if (!UpdaterExtension::download($url, $file, $error)) {
$this->response->data = [ 'total' => 0, 'msg' => __("updater.download_error"), 'details' => $error ];
if (!$updater->download($url, $file)) {
$this->response->data = [
'total' => 0,
'msg' => __("updater.download_error"),
'details' => $updater->lastErrorString ?? ''
];
return;
}
$error = null;
if (!UpdaterExtension::extractAndReplace($file, $error)) {
$this->response->data = [ 'total' => 0, 'msg' => __("updater.update_error"), 'details' => $error ];
if (!$updater->extractAndReplace($file)) {
$this->response->data = [
'total' => 0,
'msg' => __("updater.update_error"),
'details' => $updater->lastErrorString ?? ''
];
return;
}
@unlink($file);

View file

@ -0,0 +1,118 @@
<?php declare(strict_types=1);
/*
This file is a part of myTinyTodo.
(C) Copyright 2023 Max Pozdeev <maxpozdeev@gmail.com>
Licensed under the GNU GPL version 2 or any later. See file COPYRIGHT for details.
*/
namespace UpdaterExtension;
class Updater
{
public $lastErrorString = null;
public function lastVersionInfo(): ?array
{
$options = array(
'http' => array(
'header' => "Content-type: application/json\r\nUser-Agent: mytinytodo\r\n"
)
);
$context = stream_context_create($options);
set_error_handler(function ($errno, $message, $file, $line) {
throw new \ErrorException($message, $errno, $errno, $file, $line);
});
$json = null;
$this->lastErrorString = null;
try {
$json = @file_get_contents("https://api.github.com/repos/maxpozdeev/mytinytodo/releases/latest", false, $context);
}
catch (\Exception $e) {
$this->lastErrorString = boolval(ini_get('html_errors')) ? htmlspecialchars_decode($e->getMessage()) : $e->getMessage();
}
restore_error_handler();
if ($json === false || $json == '') {
error_log("Failed to get last version info: ".$this->lastErrorString);
return null;
}
$a = json_decode($json, true) ?? [];
$ret = [];
if ( isset($a['name']) && isset($a['assets']) &&
is_array($a['assets']) && count($a['assets']) > 0 &&
($asset = $a['assets'][0]) && isset($asset['browser_download_url']) )
{
$ret['version'] = substr($a['name'], 1); //remove first 'v'
$ret['download'] = $asset['browser_download_url'];
}
else {
error_log("HTTP response contains unexpected content");
$this->lastErrorString = "HTTP response contains unexpected content";
}
return $ret;
}
public function download(string $url, string $outfile): bool
{
$this->lastErrorString = null;
$dir = dirname($outfile);
if (!is_dir($dir) || !is_writable($dir)) {
$this->lastErrorString = "myTinyTodo directory is not writable";
return false;
}
$f = @fopen($url, 'r');
if ($f === false) {
$ea = error_get_last();
$this->lastErrorString = $ea['message'] ?? "Failed to open stream";
return false;
}
$bytes = @file_put_contents($outfile, $f, LOCK_EX);
$ea = error_get_last();
fclose($f);
if ($bytes === false) {
$this->lastErrorString = $ea['message'] ?? "Can not save file";
return false;
}
return true;
}
public function extractAndReplace(string $filename): bool
{
$this->lastErrorString = null;
$dir = MTTPATH;
if (!is_dir($dir) || !is_writable($dir)) {
$this->lastErrorString = "myTinyTodo directory is not writable";
return false;
}
$output = null;
$retval = null;
$command = "tar xzf ". escapeshellarg($filename). " --strip-components 1 -C ". escapeshellarg($dir). " 2>&1";
@exec($command, $output, $retval);
if ($retval != 0) {
$this->lastErrorString = "Failed to execute tar command ($retval): ". ($output ? implode("\n", $output) : "no output");
error_log($this->lastErrorString);
return false;
}
// Extensions
$dir = MTT_EXT;
$filename = $dir . 'extensions.tar.gz';
if (file_exists($filename)) {
if (!is_writable($dir)) {
$this->lastErrorString = "Extensions directory is not writable";
return false;
}
$command = "tar xzf ". escapeshellarg($filename). " -C ". escapeshellarg($dir). " 2>&1";
@exec($command, $output, $retval);
if ($retval != 0) {
$this->lastErrorString = "Extensions: failed to execute tar command ($retval): ". ($output ? implode("\n", $output) : "no output");
error_log($this->lastErrorString);
return false;
}
unlink($filename);
}
return true;
}
}

View file

@ -1,6 +1,6 @@
{
"bundleId": "updater",
"name": "Updates",
"version": "0.9",
"version": "0.9.1",
"description": "myTinyTodo self-updater"
}

View file

@ -1,5 +1,7 @@
{
"ext.updater.name": "Updates",
"updater.urlconfigwarning": "Enable PHP 'allow_url_fopen' directive to be able to download updates.",
"updater.tarwarning": "Update is not possible, 'tar' utility is not found.",
"updater.h_check_updates": "Check updates",
"updater.check": "Check",
"updater.no_updates": "No updates available",

View file

@ -1,5 +1,7 @@
{
"ext.updater.name": "Обновления",
"updater.urlconfigwarning": "Для получения обновлений требуется включить директиву 'allow_url_fopen' в настройках PHP .",
"updater.tarwarning": "Исполняемый файл 'tar' не найден, обновление невозможно.",
"updater.h_check_updates": "Проверка обновлений",
"updater.check": "Проверить",
"updater.no_updates": "Нет обновлений",

View file

@ -2,7 +2,7 @@
/*
This file is a part of myTinyTodo.
(C) Copyright 2022 Max Pozdeev <maxpozdeev@gmail.com>
(C) Copyright 2022-2023 Max Pozdeev <maxpozdeev@gmail.com>
Licensed under the GNU GPL version 2 or any later. See file COPYRIGHT for details.
*/
@ -11,6 +11,7 @@ if (!defined('MTTPATH')) {
}
require_once('class.controller.php');
require_once('class.updater.php');
function mtt_ext_updater_instance(): MTTExtension
{
@ -18,6 +19,7 @@ function mtt_ext_updater_instance(): MTTExtension
}
use UpdaterExtension\Controller;
use UpdaterExtension\Updater;
class UpdaterExtension extends MTTExtension implements MTTExtensionSettingsInterface, MTTHttpApiExtender
{
@ -53,31 +55,50 @@ class UpdaterExtension extends MTTExtension implements MTTExtensionSettingsInter
$version = $prefs['version'] ?? '';
$updateStr = '';
$curVersion = htmlspecialchars(mytinytodo\Version::VERSION);
$err = null;
if (time() - $lastCheck > 86400*7) {
$a = self::lastVersionInfo();
$updater = new Updater;
$a = $updater->lastVersionInfo();
if ($a) {
$lastCheck = $prefs['lastCheck'] = time();
$version = $prefs['version'] = $a['version'] ?? '';
$prefs['download'] = $a['download'] ?? '';
Config::saveDomain(self::domain, $prefs);
}
else {
$err = $updater->lastErrorString;
}
}
$warning = '';
if ($version != '') {
if ( version_compare($version, mytinytodo\Version::VERSION) > 0 ) {
$updateStr = "<br> {$e('updater.updatet_version_avaialable')}: ". htmlspecialchars($version);
# allow update to v1.7.x only
if ("1.7." == substr($version, 0, 4)) {
$updateStr .= "<br><br>\n <a href=\"#\" data-ext-settings-action=\"post:update\" data-ext=\"$ext\">{$e('updater.update')}</a> ";
}
$retval = 0;
$output = null;
unset($output);
@exec('tar --version', $output, $retval);
if ($retval != 0) {
$warning = "<div class=\"tr\"><div style=\"width:100%;text-align:center;\">⚠️ {$e('updater.tarwarning')}</div></div>";
}
}
else {
$updateStr = "<br>{$e('updater.no_updates')}";
}
}
$lastCheckStr = $lastCheck ? timestampToDatetime($lastCheck, true) : "";
$lastCheckStr = $err ? $e('updater.download_error') : ($lastCheck ? timestampToDatetime($lastCheck, true) : "");
if (!boolval(ini_get('allow_url_fopen'))) {
$warning .= "<div class=\"tr\"><div style=\"width:100%;text-align:center;\">⚠️ {$e('updater.urlconfigwarning')}</div></div>";
}
return
<<<EOD
$warning
<div class="tr">
<div class="th"> {$e('updater.h_check_updates')} </div>
<div class="td">
@ -95,101 +116,9 @@ EOD;
}
static function preferences(): array
{
$prefs = Config::requestDomain(self::domain);
return $prefs;
}
static function lastVersionInfo(): ?array
{
$options = array(
'http' => array(
'header' => "Content-type: application/json\r\nUser-Agent: mytinytodo\r\n"
)
);
$context = stream_context_create($options);
$json = @file_get_contents("https://api.github.com/repos/maxpozdeev/mytinytodo/releases/latest", false, $context);
if ($json === false || $json == '') {
return null;
}
$a = json_decode($json, true) ?? [];
$ret = [];
if ( isset($a['name']) && isset($a['assets']) &&
is_array($a['assets']) && count($a['assets']) > 0 &&
($asset = $a['assets'][0]) && isset($asset['browser_download_url']) )
{
$ret['version'] = substr($a['name'], 1); //remove first 'v'
$ret['download'] = $asset['browser_download_url'];
}
else {
error_log("Unexpected content");
}
return $ret;
}
static function download(string $url, string $outfile, string &$error = null): bool
{
$dir = dirname($outfile);
if (!is_dir($dir) || !is_writable($dir)) {
$error = "myTinyTodo directory is not writable";
return false;
}
$f = @fopen($url, 'r');
if ($f === false) {
$ea = error_get_last();
$error = ($ea && isset($ea['message'])) ? $ea['message'] : "Failed to open stream";
return false;
}
$bytes = @file_put_contents($outfile, $f, LOCK_EX);
$ea = error_get_last();
fclose($f);
if ($bytes === false) {
$error = ($ea && isset($ea['message'])) ? $ea['message'] : "Can not save file";
return false;
}
return true;
}
static function extractAndReplace(string $filename, string &$error = null): bool
{
$dir = MTTPATH;
if (!is_dir($dir) || !is_writable($dir)) {
$error = "myTinyTodo directory is not writable";
return false;
}
$output = null;
$retval = null;
$command = "tar xzf ". escapeshellarg($filename). " --strip-components 1 -C ". escapeshellarg($dir). " 2>&1";
@exec($command, $output, $retval);
if ($retval != 0) {
$error = "Failed to execute tar command ($retval): ". ($output ? implode("\n", $output) : "no output");
error_log($error);
return false;
}
// Extensions
$dir = MTT_EXT;
$filename = $dir . 'extensions.tar.gz';
if (file_exists($filename)) {
if (!is_writable($dir)) {
$error = "Extensions directory is not writable";
return false;
}
$command = "tar xzf ". escapeshellarg($filename). " -C ". escapeshellarg($dir). " 2>&1";
@exec($command, $output, $retval);
if ($retval != 0) {
$error = "Extensions: failed to execute tar command ($retval): ". ($output ? implode("\n", $output) : "no output");
error_log($error);
return false;
}
unlink($filename);
}
return true;
}
}