From b39e1e91af85e679194c39b9b6c605e44d224957 Mon Sep 17 00:00:00 2001 From: Dmitriy Zayceff Date: Mon, 12 Mar 2018 14:02:58 +0300 Subject: [PATCH] Upd. --- .../httpclient/HttpAsyncResponse.php | 55 ++ .../framework/httpclient/HttpChecker.php | 175 +++++ .../framework/httpclient/HttpClient.php | 574 ++++++++++++++++ .../framework/httpclient/HttpDownloader.php | 620 ++++++++++++++++++ .../framework/httpclient/HttpResponse.php | 236 +++++++ .../src-php/php/gui/framework/ScriptEvent.php | 25 +- wizard-core/src-php/framework/core/Event.php | 14 +- 7 files changed, 1669 insertions(+), 30 deletions(-) create mode 100644 modules/wizard-httpclient/src-php/framework/httpclient/HttpAsyncResponse.php create mode 100644 modules/wizard-httpclient/src-php/framework/httpclient/HttpChecker.php create mode 100644 modules/wizard-httpclient/src-php/framework/httpclient/HttpClient.php create mode 100644 modules/wizard-httpclient/src-php/framework/httpclient/HttpDownloader.php create mode 100644 modules/wizard-httpclient/src-php/framework/httpclient/HttpResponse.php diff --git a/modules/wizard-httpclient/src-php/framework/httpclient/HttpAsyncResponse.php b/modules/wizard-httpclient/src-php/framework/httpclient/HttpAsyncResponse.php new file mode 100644 index 0000000..4977345 --- /dev/null +++ b/modules/wizard-httpclient/src-php/framework/httpclient/HttpAsyncResponse.php @@ -0,0 +1,55 @@ +onDone = $callback; + return $this; + } + + /** + * @param callable $callback + * @return HttpAsyncResponse + */ + function whenSuccess(callable $callback) + { + $this->onSuccess = $callback; + return $this; + } + + /** + * @param callable $callback + * @return HttpAsyncResponse + */ + function whenError(callable $callback) + { + $this->onError = $callback; + return $this; + } + + /** + * @param HttpResponse $response + */ + public function trigger(HttpResponse $response) + { + if ($this->onSuccess && $response->isSuccess()) call_user_func($this->onSuccess, $response); + if ($this->onError && $response->isFail()) call_user_func($this->onError, $response); + + if ($this->onDone) call_user_func($this->onDone, $response); + } +} \ No newline at end of file diff --git a/modules/wizard-httpclient/src-php/framework/httpclient/HttpChecker.php b/modules/wizard-httpclient/src-php/framework/httpclient/HttpChecker.php new file mode 100644 index 0000000..87e4dac --- /dev/null +++ b/modules/wizard-httpclient/src-php/framework/httpclient/HttpChecker.php @@ -0,0 +1,175 @@ +client = new HttpClient(); + $this->client->userAgent = 'Http Checker 1.0'; + $this->client->readTimeout = 15000; + } + + /** + * @event addTo + * @param Event $e + */ + private function handleAddTo(Event $e) + { + if ($this->autoStart) { + $this->start(); + } + } + + protected function doInterval() + { + $active = (bool) $this->checkTimer; + $this->stop(); + + Logger::debug("http checker ping '{0}' ...", $this->url); + + $this->client->getAsync($this->url, [], function (HttpResponse $response) use ($active) { + if ($response->statusCode() == 200) { + if ($this->available !== true) { + $this->trigger(new Event('online', $this)); + } + + $this->available = true; + } else { + if ($this->available !== false) { + $this->trigger(new Event('offline', $this)); + } + + $this->available = false; + } + + if ($active) { + $this->start(); + } + }); + } + + /** + * Check status of url. + */ + public function ping() + { + $this->doInterval(); + } + + /** + * Start checker worker. + */ + public function start() + { + $this->checkTimer = Timer::every($this->_checkInterval, Invoker::of([$this, 'doInterval'])); + $this->ping(); + } + + /** + * Stop checker worker. + */ + public function stop() + { + if ($this->checkTimer) { + $this->checkTimer->cancel(); + $this->checkTimer = null; + } + } + + /** + * @return HttpClient + */ + public function client() + { + return $this->client; + } + + /** + * @return int + */ + protected function getCheckInterval() + { + return $this->_checkInterval; + } + + /** + * @param int $checkInterval + */ + protected function setCheckInterval($checkInterval) + { + $this->_checkInterval = $checkInterval; + $this->stop(); + $this->start(); + } + + /** + * @return bool + */ + public function isOffline() + { + return $this->available === false; + } + + /** + * @return bool + */ + public function isOnline() + { + return $this->available === true; + } + + public function isUnknown() + { + return $this->available === null; + } +} \ No newline at end of file diff --git a/modules/wizard-httpclient/src-php/framework/httpclient/HttpClient.php b/modules/wizard-httpclient/src-php/framework/httpclient/HttpClient.php new file mode 100644 index 0000000..c87eab7 --- /dev/null +++ b/modules/wizard-httpclient/src-php/framework/httpclient/HttpClient.php @@ -0,0 +1,574 @@ +_boundary = Str::random(90); + $this->_lock = new SharedValue(null); + } + + /** + * @param string $url + * @param mixed $data + * @return HttpResponse + */ + public function get($url, $data = null) + { + return $this->execute($url, 'GET', $data); + } + + /** + * @param string $url + * @param mixed $data + * @return HttpResponse + */ + public function post($url, $data = null) + { + return $this->execute($url, 'POST', $data); + } + + /** + * @param string $url + * @param mixed $data + * @return HttpResponse + */ + public function put($url, $data = null) + { + return $this->execute($url, 'PUT', $data); + } + + /** + * @param string $url + * @param mixed $data + * @return HttpResponse + */ + public function patch($url, $data = null) + { + return $this->execute($url, 'PATCH', $data); + } + + /** + * @param string $url + * @param mixed $data + * @return HttpResponse + */ + public function delete($url, $data = null) + { + return $this->execute($url, 'DELETE', $data); + } + + /** + * @non-getters + * @param $url + * @param mixed $data + * @param callable $callback + * @return HttpAsyncResponse + */ + public function getAsync($url, $data = null, callable $callback = null) + { + return $this->executeAsync($url, 'GET', $data, $callback); + } + + /** + * @param $url + * @param mixed $data + * @param callable $callback + * @return HttpAsyncResponse + */ + public function postAsync($url, $data = null, callable $callback = null) + { + return $this->executeAsync($url, 'POST', $data, $callback); + } + + /** + * @param $url + * @param mixed $data + * @param callable $callback + * @return HttpAsyncResponse + */ + public function putAsync($url, $data = null, callable $callback = null) + { + return $this->executeAsync($url, 'PUT', $data, $callback); + } + + /** + * @param $url + * @param mixed $data + * @param callable $callback + * @return HttpAsyncResponse + */ + public function patchAsync($url, $data = null, callable $callback = null) + { + return $this->executeAsync($url, 'PATCH', $data, $callback); + } + + /** + * @param $url + * @param mixed $data + * @param callable $callback + * @return HttpAsyncResponse + */ + public function deleteAsync($url, $data = null, callable $callback = null) + { + return $this->executeAsync($url, 'DELETE', $data, $callback); + } + + /** + * @param string $url + * @param string $method + * @param array|mixed $data + * @param callable $callback + * @return HttpAsyncResponse + */ + public function executeAsync($url, $method = 'GET', $data = null, callable $callback = null) + { + $response = new HttpAsyncResponse(); + + (new Thread(function () use ($url, $method, $data, $response, $callback) { + $httpResponse = $this->execute($url, $method, $data); + + $response->trigger($httpResponse); + + if ($callback) { + $callback($httpResponse); + } + }))->start(); + + return $response; + } + + /** + * @param string $url + * @param string $method + * @param array|mixed $data + * @return HttpResponse + */ + public function execute($url, $method = 'GET', $data = null) + { + $connect = null; + $body = null; + + $this->_lock->synchronize(function () use (&$connect, &$body, $url, $method, $data) { + $existsBody = false; + + switch ($method) { + case 'PUT': + case 'POST': + case 'PATCH': + $existsBody = true; + break; + + default: + $data = flow((array)$this->data, (array)$data)->toMap(); + + if ($data) { + $url .= "?" . $this->formatUrlencode($data); + } + + break; + } + + $proxy = null; + + if (!is_array($this->cookies)) { + $this->cookies = self::textToArray($this->cookies); + } + + if (!is_array($this->headers)) { + $this->headers = self::textToArray($this->headers); + } + + if ($this->proxy) { + list($proxyHost, $proxyPort) = str::split($this->proxy, ':', 2); + $proxy = new Proxy($this->proxyType, $proxyHost, $proxyPort); + } + + $url = "{$this->baseUrl}{$url}"; + + $connect = URLConnection::create($url, $proxy); + + $connect->connectTimeout = $this->connectTimeout; + $connect->followRedirects = $this->followRedirects; + $connect->readTimeout = $this->readTimeout; + $connect->requestMethod = $method; + $connect->doInput = true; + $connect->doOutput = true; + + if ($this->referrer) { + $connect->setRequestProperty('Referrer', $this->referrer); + } + + $connect->setRequestProperty('User-Agent', $this->userAgent); + + if ($existsBody) { + switch (str::upper($this->requestType)) { + case 'NONE': + break; + + case 'JSON': + $connect->setRequestProperty('Content-Type', "application/json; charset=UTF-8"); + $data = flow((array)$this->data, (array)$data)->toMap(); + $body = (new JsonProcessor())->format($data); + break; + + case 'TEXT': + $connect->setRequestProperty('Content-Type', 'text/html'); + $body = $data !== null ? "$data" : "$this->data"; + break; + + case 'URLENCODE': + $connect->setRequestProperty('Cache-Control', 'no-cache'); + $connect->setRequestProperty('Content-Type', 'application/x-www-form-urlencoded'); + + if (!is_array($this->data)) { + $this->data = self::textToArray($this->data); + } + + $data = Flow::of((array)$this->data)->append((array)$data)->withKeys()->toArray(); + + $body = $this->formatUrlencode($data); + break; + + case 'MULTIPART': + $connect->setRequestProperty('Cache-Control', 'no-cache'); + $connect->setRequestProperty('Content-Type', 'multipart/form-data; boundary=' . $this->_boundary); + + if (!is_array($this->data)) { + $this->data = self::textToArray($this->data); + } + + $data = Flow::of((array)$this->data)->append((array)$data)->withKeys()->toArray(); + $body = $this->formatMultipart($data); + break; + } + } + + $cookie = []; + foreach ($this->cookies as $name => $value) { + $value = urlencode($value); + $cookie[] = "$name=$value"; + } + + if ($cookie) { + $connect->setRequestProperty('Cookie', str::join($cookie, '; ')); + } + + foreach ($this->headers as $name => $value) { + $connect->setRequestProperty($name, $value); + } + + Logger::debug("Request {$method} -> {$url}"); + }); + + return $this->connect($connect, $body); + } + + protected function connect(URLConnection $connection, $body) + { + $response = new HttpResponse(); + + try { + if ($body) { + if ($body instanceof Stream) { + $readFully = $body->readFully(); + $connection->setRequestProperty('Content-Length', str::length($readFully)); + $connection->getOutputStream()->write($readFully); + $readFully = null; + } else { + $connection->setRequestProperty('Content-Length', str::length($body)); + $connection->getOutputStream()->write($body); + } + } + + $connection->connect(); + $inStream = $connection->getInputStream(); + } catch (IOException $e) { + $inStream = $connection->getErrorStream(); + } catch (SocketException $e) { + $response->statusCode(500); + $response->statusMessage($e->getMessage()); + $inStream = new MemoryStream(); + } catch (\Exception $e) { + $response->statusCode(599); + $response->statusMessage($e->getMessage()); + $inStream = new MemoryStream(); + } + + $body = null; + + switch ($this->responseType) { + case 'JSON': + $data = $inStream->readFully(); + try { + $body = (new JsonProcessor(JsonProcessor::DESERIALIZE_AS_ARRAYS))->parse($data); + } catch (ProcessorException $e) { + $response->statusCode(400); + $response->statusMessage($e->getMessage()); + } + break; + + case 'TEXT': + $data = $inStream->readFully(); + $body = $data; + break; + + case 'STREAM': + $body = $inStream; + break; + } + + $response->body($body); + + try { + if ($response->statusCode() != 500) { + $response->statusCode($connection->responseCode); + $response->statusMessage($connection->responseMessage); + $response->headers($connection->getHeaderFields()); + } + } catch (IOException $e) { + $response->statusCode(500); + $response->statusMessage($e->getMessage()); + } catch (SocketException $e) { + $response->statusCode(500); + $response->statusMessage($e->getMessage()); + } catch (\Exception $e) { + $response->statusCode(599); + $response->statusMessage($e->getMessage()); + } + + if ($response->isSuccess()) { + $this->trigger(new Event('success', $this, null, ['response' => $response])); + } + + if ($response->isFail()) { + $this->trigger(new Event('error', $this, null, ['response' => $response])); + } + + if ($response->isServerError()) { + $this->trigger(new Event('errorServer', $this, null, ['response' => $response])); + } + + if ($response->isNotFound()) { + $this->trigger(new Event('errorNotFound', $this, null, ['response' => $response])); + } + + if ($response->isAccessDenied()) { + $this->trigger(new Event('errorAccessDenied', $this, null, ['response' => $response])); + } + + return $response; + } + + /** + * @param array $data + * @param string $prefix + * @return string + */ + private function formatUrlencode(array $data, $prefix = '') + { + $str = []; + + foreach ($data as $code => $value) { + if (is_array($value)) { + $str[] = $this->formatUrlencode($value, $prefix ? "{$prefix}[$code]" : $code); + } else { + if ($prefix) { + $str[] = "{$prefix}[$code]=" . urlencode($value); + } else { + $str[] = "$code=" . urlencode($value); + } + } + } + + return str::join($str, '&'); + } + + private function formatMultipart(array $data, $prefix = '') + { + $streams = []; + + $out = new MemoryStream(); + + foreach ($data as $name => $value) { + if ($value instanceof File) { + $streams[$name] = new FileStream($value); + } else if ($value instanceof Stream) { + $streams[$name] = $value; + } else { + $out->write("--"); + $out->write($this->_boundary); + $out->write(self::CRLF); + + $name = urlencode($name); + $out->write("Content-Disposition: form-data; name=\"$name\""); + $out->write(self::CRLF); + + $out->write("Content-Type: text/plain; charset={$this->encoding}"); + $out->write(self::CRLF); + $out->write(self::CRLF); + + $out->write("$value"); + $out->write(self::CRLF); + } + } + + foreach ($streams as $name => $stream) { + /** @var Stream $stream */ + $out->write("--"); + $out->write($this->_boundary); + $out->write(self::CRLF); + + $name = urlencode($name); + $out->write("Content-Disposition: form-data; name=\"$name\"; filename=\""); + $out->write(urlencode(fs::name($stream->getPath())) . "\""); + $out->write(self::CRLF); + + $out->write("Content-Type: "); + $out->write(URLConnection::guessContentTypeFromName($stream->getPath())); + $out->write(self::CRLF); + + $out->write("Content-Transfer-Encoding: binary"); + $out->write(self::CRLF); + $out->write(self::CRLF); + + $out->write($stream->readFully()); + $out->write(self::CRLF); + + $stream->close(); + } + + $out->write("--$this->_boundary--"); + $out->write(self::CRLF); + $out->seek(0); + + return $out; + } + + protected static function textToArray($text, $trimValues = false) + { + $scanner = new Scanner($text); + $result = []; + + while ($scanner->hasNextLine()) { + list($key, $value) = str::split($scanner->nextLine(), '=', 2); + $result[$key] = $trimValues ? str::trim($value) : $value; + } + + return $result; + } +} diff --git a/modules/wizard-httpclient/src-php/framework/httpclient/HttpDownloader.php b/modules/wizard-httpclient/src-php/framework/httpclient/HttpDownloader.php new file mode 100644 index 0000000..5eda3ce --- /dev/null +++ b/modules/wizard-httpclient/src-php/framework/httpclient/HttpDownloader.php @@ -0,0 +1,620 @@ +client = new HttpClient(); + $this->client->responseType = 'STREAM'; + $this->client->requestType = 'NONE'; + + $this->_downloadedFiles = new SharedStack([]); + $this->_progressFiles = new SharedMap([]); + $this->_failedFiles = new SharedStack([]); + + $this->_urlStats = new SharedMap([]); + } + + protected function checkBreak() + { + if ($this->_break) { + throw new HttpDownloaderBreakException(); + } + } + + /** + * @param string $url + * @param string $fileName + * @return HttpResponse + * @throws HttpDownloaderBreakException + */ + public function download(string $url, string $fileName = null): HttpResponse + { + $this->checkBreak(); + + $response = $this->client->get($url); + + $this->checkBreak(); + + if ($response->isSuccess()) { + $stream = $response->body(); + + $contentLength = $response->contentLength(); + + if ($fileName == null) { + $url = str::split($url, '?')[0]; + + $fileName = urldecode(fs::name($url)); + + $contentDisposition = $response->header('Content-Disposition'); + + foreach (str::split($contentDisposition, ';', 15) as $one) { + list($name, $value) = str::split(trim($one), '=', 2); + + if ($value && $name == 'filename') { + if ($value[0] == '"') $value = str::sub($value, 1); + if ($value[str::length($value) - 1] == '"') $value = str::sub($value, 0, str::length($value) - 1); + + $fileName = urldecode($value); + } + + if ($value && $name == 'size') { + $contentLength = (int) $value; + } + } + } + + $fileName = fs::normalize($this->destDirectory ? "$this->destDirectory/$fileName" : $fileName); + + $this->_urlStats->set($url, [ + 'url' => $url, 'response' => $response, 'file' => $fileName, 'size' => $contentLength, 'progress' => 0, 'status' => 'DOWNLOADING' + ]); + + uiLater(function () use ($contentLength, $url, $fileName, $response) { + $this->trigger(new Event( + 'progress', $this, null, ['url' => $url, 'response' => $response, 'file' => $fileName, 'max' => $contentLength, 'progress' => 0] + )); + }); + + try { + fs::copy($stream, $this->useTempFile ? $fileName . self::TEMP_EXT : $fileName, function ($progress, $bytes) use ($contentLength, $url, $fileName, $response) { + $this->checkBreak(); + + $this->_downloadedBytes += $bytes; + + $this->setStatValue($url, 'progress', $progress); + + $this->trigger(new Event( + 'progress', $this, null, ['url' => $url, 'response' => $response, 'file' => $fileName, 'max' => $contentLength, 'progress' => $progress] + )); + }, $this->bufferSize); + } catch (IOException $e) { + $this->checkBreak(); + $response->statusCode(400); + + $this->setStatValue($url, 'status', self::STATUS_ERROR); + $this->setStatValue($url, 'error', $e->getMessage()); + + $this->_urlStats->set($url, ['url' => $url, 'file' => $fileName, 'size' => $contentLength, 'progress' => 0, 'status' => 'DOWNLOADING']); + + $this->trigger(new Event('errorOne', $this, null, ['url' => $url, 'file' => $fileName, 'response' => $response, 'error' => $e->getMessage()])); + return $response; + } + + $this->checkBreak(); + + if ($this->useTempFile) { + if (fs::exists($fileName)) { + fs::clean($fileName); + fs::delete($fileName); + } + + if (!File::of($fileName . self::TEMP_EXT)->renameTo($fileName)) { + $this->checkBreak(); + + $response->statusCode(400); + + $this->setStatValue($url, 'status', self::STATUS_ERROR); + $this->setStatValue($url, 'error', 'Cannot rename file'); + + $this->trigger(new Event( + 'errorOne', + $this, + null, + ['url' => $url, 'file' => $fileName, 'response' => $response, 'error' => 'Cannot rename file'] + )); + } + + fs::delete($fileName . self::TEMP_EXT); + } + + $this->setStatValue($url, 'status', self::STATUS_SUCCESS); + + $this->trigger(new Event('successOne', $this, null, ['url' => $url, 'file' => $fileName, 'response' => $response])); + } else { + $this->checkBreak(); + $this->trigger(new Event('errorOne', $this, null, ['url' => $url, 'file' => $fileName, 'response' => $response])); + } + + return $response; + } + + /** + * @param $url + * @param string $name + * @param $value + */ + protected function setStatValue($url, string $name, $value) + { + $stat = (array) $this->_urlStats->get($url); + $stat[$name] = $value; + $this->_urlStats->set($url, $stat); + } + + /** + * Stop all downloads. + */ + public function stop() + { + $this->_break = true; + + if ($this->_threadPool) { + $this->_threadPool->shutdown(); + $this->_threadPool = null; + } + } + + /** + * @return int + */ + public function getSpeed(): int + { + $time = Time::seconds() - $this->_startTime; + + $speed = $this->_downloadedBytes / $time; + + return round($speed); + } + + /** + * @return float + */ + public function getBitSpeed(): float + { + return round($this->getSpeed() / 8); + } + + /** + * @param callable $callback + */ + public function stopAsync(callable $callback) + { + (new Thread(function () use ($callback) { + $this->stopAndWait(); + + if ($callback) { + $callback(); + } + }))->start(); + } + + public function stopAndWait() + { + $this->stop(); + + while ($this->_busy) ; + } + + /** + * @var bool + */ + protected $lazyStart = false; + + /** + * Start downloading. + */ + public function start() + { + if ($this->_busy) { + if ($this->lazyStart) return; + + $this->lazyStart = true; + + $this->stopAsync(function () { + $this->start(); + $this->lazyStart = false; + }); + //throw new \Exception("Cannot download, downloader is busy, use stopAndWait() method to stop it before start."); + } + + //new HttpResponse(); + //new HttpAsyncResponse(); + + $this->_startTime = Time::seconds(); + $this->_downloadedBytes = 0; + + $this->_downloadedFiles->clear(); + $this->_failedFiles->clear(); + $this->_progressFiles->clear(); + + $this->_urlStats->clear(); + + $this->_busy = true; + $this->_break = false; + + $urls = $this->getAllUrls(); + + $this->_threadPool = $thPool = ThreadPool::createFixed($this->threadCount); + + $countDone = new SharedValue(null); + + foreach ($urls as $name => $url) { + $this->_urlStats->set($url, ['url' => $url, 'status' => self::STATUS_WAIT]); + + if ($this->_break) { + break; + } + + $thPool->submit(function () use ($url, $name, $urls, $countDone) { + try { + $this->_progressFiles->set($url, $url); + + $response = $this->download($url, is_string($name) ? $name : null); + + $this->_progressFiles->remove($url); + + if ($response->isSuccess()) { + $this->_downloadedFiles->push($url); + } else { + $this->_failedFiles->push($url); + + if ($this->breakOnError) { + $this->stop(); + $this->checkBreak(); + } + } + } catch (HttpDownloaderBreakException $e) { + ; + } + + + if ($countDone->setAndGet(function ($v) { return $v+1; }) == sizeof($urls)) { + if (!$this->_break) { + if ($this->_failedFiles->isEmpty()) { + $this->trigger(new Event('successAll', $this)); + } + } else { + // ... + } + + $this->trigger(new Event('done', $this)); + + if ($this->_threadPool) { + $this->_threadPool->shutdown(); + } + + $this->_busy = false; + } + }); + } + } + + public function free() + { + parent::free(); + + $this->stop(); + } + + public function __destruct() + { + $this->stop(); + } + + /** + * @return bool + */ + public function isBusy(): bool + { + return $this->_busy; + } + + /** + * @return bool + */ + public function isBreak(): bool + { + return $this->_break; + } + + /** + * @param string $url + * @return mixed + */ + public function getUrlInfo($url): array + { + return $this->_urlStats->get($url); + } + + /** + * @param string $url + * @return string + */ + public function getUrlStatus($url): string + { + return $this->getUrlInfo($url)['status']; + } + + /** + * @param string $url + * @return int + */ + public function getUrlSize($url): int + { + return (int) $this->getUrlInfo($url)['size'] ?: -1; + } + + /** + * @param int $url + * @return float|int + */ + public function getUrlProgress($url): float + { + $size = $this->getUrlSize($url); + $progress = (int) $this->getUrlInfo($url)['progress']; + + if ($progress == 0) return 0; + + return $progress / $size; + } + + /** + * @param string $url + * @return bool + */ + public function isUrlSuccess($url): bool + { + return $this->getUrlInfo($url)['status'] == self::STATUS_SUCCESS; + } + + /** + * @param string $url + * @return bool + */ + public function isUrlWaiting($url): bool + { + return $this->getUrlInfo($url)['status'] == self::STATUS_WAIT; + } + + /** + * @param string $url + * @return bool + */ + public function isUrlDownloading($url): bool + { + return $this->getUrlInfo($url)['status'] == self::STATUS_DOWNLOADING; + } + + /** + * @param string $url + * @return bool + */ + public function isUrlError($url): bool + { + return $this->getUrlInfo($url)['status'] == self::STATUS_ERROR; + } + + /** + * @param string $url + * @return bool + */ + public function isUrlDone($url): bool + { + return $this->isUrlSuccess($url) || $this->isUrlError($url); + } + + + /** + * @return array + */ + public function getDownloadedUrls(): array + { + return arr::toArray($this->_downloadedFiles); + } + + /** + * @return array + */ + public function getFailedUrls(): array + { + return arr::toArray($this->_failedFiles); + } + + /** + * @return array + */ + public function getProgressUrls(): array + { + return arr::toArray($this->_progressFiles); + } + + /** + * @return array + */ + public function getAllUrls(): array + { + $urls = $this->urls; + + if (!is_array($urls)) { + $urls = self::textToArray($urls, true); + } + + return $urls; + } + + /** + * @return HttpClient + */ + public function client(): HttpClient + { + return $this->client; + } + + /** + * Load urls from file, url and other stream. + * @param string $source + * @param string $encoding + */ + public function loadUrls($source, $encoding = 'UTF-8') + { + $this->urls = fs::get($source, $encoding); + } + + private static function textToArray($text, $trimValues = false) + { + $scanner = new Scanner($text); + $result = []; + + while ($scanner->hasNextLine()) { + $line = $scanner->nextLine(); + + $posEq = str::pos($line, '='); + $posQ = str::pos($line, '?'); + + if (str::trim($line)) { + if ($posEq < $posQ) { + list($key, $value) = str::split($line, '=', 2); + } else { + $key = $line; + $value = null; + } + + if ($value) { + $result[$key] = $trimValues ? str::trim($value) : $value; + } else { + $result[] = $trimValues ? str::trim($key) : $key; + } + } + } + + return $result; + } +} \ No newline at end of file diff --git a/modules/wizard-httpclient/src-php/framework/httpclient/HttpResponse.php b/modules/wizard-httpclient/src-php/framework/httpclient/HttpResponse.php new file mode 100644 index 0000000..88c54d1 --- /dev/null +++ b/modules/wizard-httpclient/src-php/framework/httpclient/HttpResponse.php @@ -0,0 +1,236 @@ +body = $data; + } else { + return $this->body; + } + } + + /** + * @param int $responseCode + * @return int + */ + public function statusCode($responseCode = null) + { + if ($responseCode) { + $this->responseCode = $responseCode; + } else { + return $this->responseCode; + } + } + + /** + * @param string $statusMessage + * @return string + */ + public function statusMessage($statusMessage = null) + { + if ($statusMessage) { + $this->statusMessage = $statusMessage; + } else { + return $this->statusMessage; + } + } + + /** + * @param array $headerFields + * @return array + */ + public function headers(array $headerFields = null) + { + if ($headerFields) { + foreach ($headerFields as $name => $value) { + if (is_array($value) && sizeof($value) == 1) $value = arr::first($value); + + $this->headers[str::lower($name)] = $value; + } + } else { + return $this->headers; + } + } + + /** + * Returns header value. + * @param string $name + * @return mixed + */ + public function header($name) + { + return $this->headers[str::lower($name)]; + } + + /** + * Returns Content-Type header value. + * @param string $contentType + * @return string + */ + public function contentType($contentType = null) + { + if ($contentType === null) { + return $this->header('Content-Type'); + } else { + $this->headers['content-type'] = $contentType; + } + } + + /** + * Content-Length header value, returns -1 if it does not exist. + * @param int $size + * @return int + */ + public function contentLength($size = null) + { + if ($size === null) { + return (int) ($this->header('Content-Length') ?: -1); + } else { + $this->headers['content-length'] = $size; + } + } + + /** + * @param string $name + * @return string|array + */ + public function cookie($name) + { + return $this->cookies()[$name]; + } + + /** + * Return array of Set-Cookie header. + * @param array $data + * @return array + */ + public function cookies(array $data = null) + { + if ($data === null) { + if ($this->cookies) { + return $this->cookies; + } + + $cookies = $this->header('Set-Cookie'); + $result = []; + + if (!is_array($cookies)) $cookies = [$cookies]; + + foreach ($cookies as $cookie) { + list($name, $value) = str::split($cookie, '=', 2); + + $values = str::split($value, ';', 10); + + $result[$name] = urldecode($values[0]); + } + + return $this->cookies = $result; + } else { + $str = []; + + foreach ($data as $name => $value) { + if (!is_array($value)) $value = [$value]; + + foreach ($value as $one) { + $one = urlencode($one); + $str[] = "$name=$one"; + } + } + + $this->cookies = $data; + $this->headers['cookie'] = str::split($str, '&'); + } + } + + /** + * Check http code >= 200 and <= 399 + * @return bool + */ + public function isSuccess() + { + $statusCode = $this->statusCode(); + return $statusCode >= 200 && $statusCode <= 399; + } + + /** + * Check http code >= 400 + * @return bool + */ + public function isFail() + { + $statusCode = $this->statusCode(); + return $statusCode >= 400; + } + + /** + * Check http code >= 400 + * @return bool + */ + public function isError() + { + return $this->isFail(); + } + + /** + * Check http code is 404 + * @return bool + */ + public function isNotFound() + { + return $this->statusCode() == 404; + } + + /** + * Check http code is 403 + * @return bool + */ + public function isAccessDenied() + { + return $this->statusCode() == 403; + } + + /** + * Check http code is 405 + * @return bool + */ + public function isInvalidMethod() + { + return $this->statusCode() == 405; + } + + /** + * Check http code >= 500 + * @return bool + */ + public function isServerError() + { + return $this->statusCode() >= 500; + } +} \ No newline at end of file diff --git a/wizard-core-legacy/src-php/php/gui/framework/ScriptEvent.php b/wizard-core-legacy/src-php/php/gui/framework/ScriptEvent.php index f9108dd..bdaf609 100644 --- a/wizard-core-legacy/src-php/php/gui/framework/ScriptEvent.php +++ b/wizard-core-legacy/src-php/php/gui/framework/ScriptEvent.php @@ -27,21 +27,16 @@ class ScriptEvent extends Event */ public $usage = 0; - /** - * @var bool - */ - private $consumed = false; - /** * ScriptEvent constructor. * @param AbstractScript $sender * @param null $target */ - public function __construct(AbstractScript $sender = null, $target = null) + /*public function __construct($sender = null, $target = null) { $this->sender = $sender; $this->target = $target ?: $sender; - } + }*/ public function done() @@ -53,20 +48,4 @@ public function isDone() { return $this->usage <= 0; } - - /** - * Consume event. - */ - public function consume() - { - $this->consumed = true; - } - - /** - * @return bool - */ - public function isConsumed() - { - return $this->consumed; - } } \ No newline at end of file diff --git a/wizard-core/src-php/framework/core/Event.php b/wizard-core/src-php/framework/core/Event.php index f5ccc20..3eed0f6 100644 --- a/wizard-core/src-php/framework/core/Event.php +++ b/wizard-core/src-php/framework/core/Event.php @@ -6,8 +6,8 @@ * Class Event * @package framework\core * - * @property Component $sender - * @property Component|null $context + * @property Component|object $sender + * @property Component|object|null $context * @property string $type * @property array $data */ @@ -19,7 +19,7 @@ class Event private $type; /** - * @var Component + * @var Component|object */ private $sender; @@ -29,7 +29,7 @@ class Event private $consumed = false; /** - * @var Component|null + * @var Component|object */ private $context; @@ -41,11 +41,11 @@ class Event /** * Event constructor. * @param string $type - * @param Component $sender - * @param Component|null $context + * @param object $sender + * @param object|null $context * @param array|null $data */ - public function __construct(string $type, Component $sender, ?Component $context = null, ?array $data = null) + public function __construct(string $type, object $sender, ?object $context = null, ?array $data = null) { $this->type = $type; $this->sender = $sender;