2022-12-30 21:54:44 +01:00
|
|
|
<?php
|
|
|
|
|
|
|
|
declare(strict_types=1);
|
2022-12-30 19:25:28 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Tools module.
|
|
|
|
*
|
|
|
|
* This file is part of MadelineProto.
|
|
|
|
* MadelineProto is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
|
|
|
* MadelineProto is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
|
|
* See the GNU Affero General Public License for more details.
|
|
|
|
* You should have received a copy of the GNU General Public License along with MadelineProto.
|
|
|
|
* If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*
|
|
|
|
* @author Daniil Gentili <daniil@daniil.it>
|
2023-01-04 12:43:01 +01:00
|
|
|
* @copyright 2016-2023 Daniil Gentili <daniil@daniil.it>
|
2022-12-30 19:25:28 +01:00
|
|
|
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
|
|
|
|
* @link https://docs.madelineproto.xyz MadelineProto documentation
|
|
|
|
*/
|
|
|
|
|
|
|
|
namespace danog\MadelineProto;
|
|
|
|
|
2023-01-11 18:47:27 +01:00
|
|
|
use Amp\Cancellation;
|
|
|
|
use Amp\CancelledException;
|
2022-12-30 20:24:13 +01:00
|
|
|
use Amp\DeferredFuture;
|
|
|
|
use Amp\Future;
|
2022-12-30 21:43:58 +01:00
|
|
|
use Amp\TimeoutCancellation;
|
2022-12-30 19:25:28 +01:00
|
|
|
use Amp\TimeoutException;
|
2023-01-15 20:13:47 +01:00
|
|
|
use Closure;
|
2022-12-30 19:25:28 +01:00
|
|
|
use Generator;
|
2023-01-21 21:21:35 +01:00
|
|
|
use Revolt\EventLoop;
|
2022-12-30 21:54:44 +01:00
|
|
|
use Throwable;
|
2022-12-30 19:25:28 +01:00
|
|
|
|
|
|
|
use const LOCK_NB;
|
|
|
|
use const LOCK_UN;
|
2022-12-30 20:24:13 +01:00
|
|
|
use function Amp\async;
|
2022-12-30 19:25:28 +01:00
|
|
|
use function Amp\ByteStream\getOutputBufferStream;
|
|
|
|
use function Amp\ByteStream\getStdin;
|
|
|
|
use function Amp\ByteStream\getStdout;
|
|
|
|
use function Amp\delay;
|
2022-12-30 21:43:58 +01:00
|
|
|
use function Amp\Future\await;
|
2022-12-30 21:54:44 +01:00
|
|
|
|
2022-12-30 21:43:58 +01:00
|
|
|
use function Amp\Future\awaitAny;
|
|
|
|
use function Amp\Future\awaitFirst;
|
2022-12-30 19:25:28 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Async tools.
|
|
|
|
*/
|
|
|
|
abstract class AsyncTools extends StrTools
|
|
|
|
{
|
2023-02-06 15:14:02 +01:00
|
|
|
/**
|
|
|
|
* Rethrow exception into event loop.
|
|
|
|
*/
|
|
|
|
public static function rethrow(\Throwable $e): void
|
|
|
|
{
|
|
|
|
EventLoop::queue(fn () => throw $e);
|
|
|
|
}
|
2022-12-30 19:25:28 +01:00
|
|
|
/**
|
2022-12-30 20:24:13 +01:00
|
|
|
* Synchronously wait for a Future|generator.
|
|
|
|
*
|
|
|
|
* @deprecated Coroutines are deprecated since amp v3
|
2023-01-27 19:00:11 +01:00
|
|
|
* @param Generator|Future $promise The promise to wait for
|
2022-12-30 19:25:28 +01:00
|
|
|
*/
|
2023-01-27 19:00:11 +01:00
|
|
|
public static function wait(Generator|Future $promise)
|
2022-12-30 19:25:28 +01:00
|
|
|
{
|
|
|
|
if ($promise instanceof Generator) {
|
2022-12-30 20:24:13 +01:00
|
|
|
return self::call($promise)->await();
|
|
|
|
} elseif (!$promise instanceof Future) {
|
2022-12-30 19:25:28 +01:00
|
|
|
return $promise;
|
|
|
|
}
|
2022-12-30 20:24:13 +01:00
|
|
|
return $promise->await();
|
2022-12-30 19:25:28 +01:00
|
|
|
}
|
|
|
|
/**
|
|
|
|
* Returns a promise that succeeds when all promises succeed, and fails if any promise fails.
|
|
|
|
* Returned promise succeeds with an array of values used to succeed each contained promise, with keys corresponding to the array of promises.
|
|
|
|
*
|
2022-12-30 20:24:13 +01:00
|
|
|
* @deprecated Coroutines are deprecated since amp v3
|
2023-01-27 19:00:11 +01:00
|
|
|
* @param array<(Generator|Future)> $promises Promises
|
2022-12-30 19:25:28 +01:00
|
|
|
*/
|
2022-12-30 21:43:58 +01:00
|
|
|
public static function all(array $promises)
|
2022-12-30 19:25:28 +01:00
|
|
|
{
|
2023-01-13 14:36:10 +01:00
|
|
|
return await(\array_map(self::call(...), $promises));
|
2022-12-30 19:25:28 +01:00
|
|
|
}
|
|
|
|
/**
|
|
|
|
* Returns a promise that is resolved when all promises are resolved. The returned promise will not fail.
|
|
|
|
*
|
2022-12-30 20:24:13 +01:00
|
|
|
* @deprecated Coroutines are deprecated since amp v3
|
|
|
|
* @param array<(Future|Generator)> $promises Promises
|
2022-12-30 19:25:28 +01:00
|
|
|
*/
|
2022-12-30 21:43:58 +01:00
|
|
|
public static function any(array $promises)
|
2022-12-30 19:25:28 +01:00
|
|
|
{
|
2023-01-13 14:36:10 +01:00
|
|
|
return awaitAny(\array_map(self::call(...), $promises));
|
2022-12-30 19:25:28 +01:00
|
|
|
}
|
|
|
|
/**
|
|
|
|
* Resolves with a two-item array delineating successful and failed Promise results.
|
|
|
|
* The returned promise will only fail if the given number of required promises fail.
|
|
|
|
*
|
2022-12-30 20:24:13 +01:00
|
|
|
* @deprecated Coroutines are deprecated since amp v3
|
|
|
|
* @param array<(Future|Generator)> $promises Promises
|
2022-12-30 19:25:28 +01:00
|
|
|
*/
|
2022-12-30 21:43:58 +01:00
|
|
|
public static function some(array $promises)
|
2022-12-30 19:25:28 +01:00
|
|
|
{
|
2023-01-13 14:36:10 +01:00
|
|
|
return await(\array_map(self::call(...), $promises));
|
2022-12-30 19:25:28 +01:00
|
|
|
}
|
|
|
|
/**
|
|
|
|
* Returns a promise that succeeds when the first promise succeeds, and fails only if all promises fail.
|
|
|
|
*
|
2022-12-30 20:24:13 +01:00
|
|
|
* @deprecated Coroutines are deprecated since amp v3
|
|
|
|
* @param array<(Future|Generator)> $promises Promises
|
2022-12-30 19:25:28 +01:00
|
|
|
*/
|
2022-12-30 21:43:58 +01:00
|
|
|
public static function first(array $promises)
|
2022-12-30 19:25:28 +01:00
|
|
|
{
|
2023-01-13 14:36:10 +01:00
|
|
|
return awaitFirst(\array_map(self::call(...), $promises));
|
2022-12-30 19:25:28 +01:00
|
|
|
}
|
|
|
|
/**
|
2023-01-27 19:00:11 +01:00
|
|
|
* Create an artificial timeout for any Generator or Promise.
|
2022-12-30 19:25:28 +01:00
|
|
|
*
|
2022-12-30 20:24:13 +01:00
|
|
|
* @deprecated Coroutines are deprecated since amp v3
|
2022-12-30 21:43:58 +01:00
|
|
|
* @param int $timeout In milliseconds
|
2022-12-30 19:25:28 +01:00
|
|
|
*/
|
2023-01-27 19:00:11 +01:00
|
|
|
public static function timeout(Generator|Future $promise, int $timeout): mixed
|
2022-12-30 19:25:28 +01:00
|
|
|
{
|
2022-12-30 21:43:58 +01:00
|
|
|
return self::call($promise)->await(new TimeoutCancellation($timeout/1000));
|
2022-12-30 19:25:28 +01:00
|
|
|
}
|
|
|
|
/**
|
|
|
|
* Creates an artificial timeout for any `Promise`.
|
|
|
|
*
|
|
|
|
* If the promise is resolved before the timeout expires, the result is returned
|
|
|
|
*
|
|
|
|
* If the timeout expires before the promise is resolved, a default value is returned
|
|
|
|
*
|
2022-12-30 20:24:13 +01:00
|
|
|
* @deprecated Coroutines are deprecated since amp v3
|
2022-12-30 19:25:28 +01:00
|
|
|
* @template TReturnAlt
|
|
|
|
* @template TReturn
|
2023-01-27 19:00:11 +01:00
|
|
|
* @template TGenerator of Generator<mixed, mixed, mixed, TReturn>
|
|
|
|
* @param Future<TReturn>|TGenerator $promise Promise to which the timeout is applied.
|
2022-12-30 20:24:13 +01:00
|
|
|
* @param int $timeout Timeout in milliseconds.
|
|
|
|
* @param TReturnAlt $default
|
2022-12-30 21:43:58 +01:00
|
|
|
* @return TReturn|TReturnAlt
|
2022-12-30 19:25:28 +01:00
|
|
|
*/
|
2022-12-30 21:43:58 +01:00
|
|
|
public static function timeoutWithDefault($promise, int $timeout, $default = null): mixed
|
2022-12-30 19:25:28 +01:00
|
|
|
{
|
2022-12-30 21:43:58 +01:00
|
|
|
try {
|
|
|
|
return self::timeout($promise, $timeout);
|
2023-01-11 18:47:27 +01:00
|
|
|
} catch (CancelledException $e) {
|
|
|
|
if (!$e->getPrevious() instanceof TimeoutException) {
|
|
|
|
throw $e;
|
|
|
|
}
|
2022-12-30 21:43:58 +01:00
|
|
|
return $default;
|
|
|
|
}
|
2022-12-30 19:25:28 +01:00
|
|
|
}
|
|
|
|
/**
|
|
|
|
* Convert generator, promise or any other value to a promise.
|
|
|
|
*
|
2022-12-30 20:24:13 +01:00
|
|
|
* @deprecated Coroutines are deprecated since amp v3
|
2022-12-30 19:25:28 +01:00
|
|
|
* @template TReturn
|
2023-01-04 16:04:05 +01:00
|
|
|
* @param Generator<mixed, mixed, mixed, TReturn>|Future<TReturn>|TReturn $promise
|
2023-01-27 19:00:11 +01:00
|
|
|
* @return Future<TReturn>
|
2022-12-30 19:25:28 +01:00
|
|
|
*/
|
2023-01-04 15:13:55 +01:00
|
|
|
public static function call(mixed $promise): Future
|
2022-12-30 19:25:28 +01:00
|
|
|
{
|
|
|
|
if ($promise instanceof Generator) {
|
2023-01-21 21:21:35 +01:00
|
|
|
return async(self::consumeGenerator(...), $promise);
|
|
|
|
}
|
|
|
|
if (!$promise instanceof Future) {
|
2022-12-30 20:24:13 +01:00
|
|
|
$f = new DeferredFuture;
|
|
|
|
$f->complete($promise);
|
|
|
|
return $f->getFuture();
|
2022-12-30 19:25:28 +01:00
|
|
|
}
|
|
|
|
return $promise;
|
|
|
|
}
|
2023-01-21 21:21:35 +01:00
|
|
|
/**
|
|
|
|
* @internal Consumes generator without creating fiber
|
|
|
|
*
|
|
|
|
*/
|
2023-01-27 19:00:11 +01:00
|
|
|
public static function consumeGenerator(Generator $g): mixed
|
2023-01-21 21:21:35 +01:00
|
|
|
{
|
|
|
|
$yielded = $g->current();
|
|
|
|
do {
|
|
|
|
while (!$yielded instanceof Future) {
|
|
|
|
if (!$g->valid()) {
|
|
|
|
return $g->getReturn();
|
|
|
|
}
|
|
|
|
if ($yielded instanceof Generator) {
|
|
|
|
$yielded = self::consumeGenerator($yielded);
|
2023-01-26 17:14:10 +01:00
|
|
|
} elseif (\is_array($yielded)) {
|
|
|
|
$yielded = \array_map(
|
2023-01-26 15:44:42 +01:00
|
|
|
fn ($v) => $v instanceof Generator ? self::consumeGenerator($v) : $v,
|
|
|
|
$yielded
|
|
|
|
);
|
|
|
|
$yielded = $g->send($yielded);
|
2023-01-21 21:21:35 +01:00
|
|
|
} else {
|
|
|
|
$yielded = $g->send($yielded);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
try {
|
|
|
|
$result = $yielded->await();
|
|
|
|
} catch (Throwable $e) {
|
|
|
|
$yielded = $g->throw($e);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
$yielded = $g->send($result);
|
|
|
|
} while (true);
|
|
|
|
}
|
2022-12-30 19:25:28 +01:00
|
|
|
/**
|
2023-06-25 16:59:56 +02:00
|
|
|
* Fork a new green thread and execute the passed function in the background.
|
|
|
|
*
|
|
|
|
* @template T
|
|
|
|
*
|
2023-06-25 17:02:20 +02:00
|
|
|
* @param \Closure(...):T $callable Function to execute
|
2023-06-25 16:59:56 +02:00
|
|
|
* @param mixed ...$args Arguments forwarded to the function when forking the thread.
|
|
|
|
*
|
|
|
|
* @return Future<T>
|
2022-12-30 19:25:28 +01:00
|
|
|
*
|
|
|
|
* @psalm-suppress InvalidScope
|
|
|
|
*/
|
2023-06-25 16:59:56 +02:00
|
|
|
public static function callFork(callable|Generator|Future $callable, ...$args): Future
|
2022-12-30 19:25:28 +01:00
|
|
|
{
|
2023-06-25 16:59:56 +02:00
|
|
|
if (\is_callable($callable)) {
|
|
|
|
$callable = async($callable, ...$args);
|
2022-12-30 19:25:28 +01:00
|
|
|
}
|
2023-06-25 16:59:56 +02:00
|
|
|
if ($callable instanceof Generator) {
|
|
|
|
$callable = self::call($callable);
|
2022-12-30 19:25:28 +01:00
|
|
|
}
|
2023-06-25 16:59:56 +02:00
|
|
|
return $callable;
|
2022-12-30 19:25:28 +01:00
|
|
|
}
|
|
|
|
/**
|
|
|
|
* Call promise in background, deferring execution.
|
|
|
|
*
|
2022-12-30 20:24:13 +01:00
|
|
|
* @deprecated Coroutines are deprecated since amp v3
|
2023-01-27 19:00:11 +01:00
|
|
|
* @param Generator|Future $promise Promise to resolve
|
2022-12-30 19:25:28 +01:00
|
|
|
*/
|
2023-01-27 19:00:11 +01:00
|
|
|
public static function callForkDefer(Generator|Future $promise): void
|
2022-12-30 19:25:28 +01:00
|
|
|
{
|
2023-01-08 18:12:58 +01:00
|
|
|
self::callFork($promise);
|
2022-12-30 19:25:28 +01:00
|
|
|
}
|
|
|
|
/**
|
|
|
|
* Call promise $b after promise $a.
|
|
|
|
*
|
2022-12-30 20:24:13 +01:00
|
|
|
* @deprecated Coroutines are deprecated since amp v3
|
2023-01-27 19:00:11 +01:00
|
|
|
* @param Generator|Future $a Promise A
|
|
|
|
* @param Generator|Future $b Promise B
|
2022-12-30 19:25:28 +01:00
|
|
|
* @psalm-suppress InvalidScope
|
|
|
|
*/
|
2023-01-27 19:00:11 +01:00
|
|
|
public static function after(Generator|Future $a, Generator|Future $b): Future
|
2022-12-30 19:25:28 +01:00
|
|
|
{
|
2022-12-30 21:43:58 +01:00
|
|
|
return async(function () use ($a, $b) {
|
|
|
|
self::call($a)->await();
|
|
|
|
return self::call($b)->await();
|
2022-12-30 19:25:28 +01:00
|
|
|
});
|
|
|
|
}
|
|
|
|
/**
|
|
|
|
* Asynchronously lock a file
|
|
|
|
* Resolves with a callbable that MUST eventually be called in order to release the lock.
|
|
|
|
*
|
|
|
|
* @param string $file File to lock
|
|
|
|
* @param integer $operation Locking mode
|
|
|
|
* @param float $polling Polling interval
|
2023-01-14 19:51:23 +01:00
|
|
|
* @param ?Cancellation $token Cancellation token
|
2023-01-15 20:13:47 +01:00
|
|
|
* @param ?Closure $failureCb Failure callback, called only once if the first locking attempt fails.
|
|
|
|
* @return ($token is null ? (Closure(): void) : ((Closure(): void)|null))
|
2022-12-30 19:25:28 +01:00
|
|
|
*/
|
2023-01-15 20:13:47 +01:00
|
|
|
public static function flock(string $file, int $operation, float $polling = 0.1, ?Cancellation $token = null, ?Closure $failureCb = null): ?Closure
|
2022-12-30 19:25:28 +01:00
|
|
|
{
|
2023-05-25 15:32:57 +02:00
|
|
|
if (!\file_exists($file)) {
|
2023-04-23 23:28:31 +02:00
|
|
|
\touch($file);
|
2022-12-30 19:25:28 +01:00
|
|
|
}
|
|
|
|
$operation |= LOCK_NB;
|
|
|
|
$res = \fopen($file, 'c');
|
|
|
|
do {
|
|
|
|
$result = \flock($res, $operation);
|
|
|
|
if (!$result) {
|
|
|
|
if ($failureCb) {
|
2023-01-21 21:21:35 +01:00
|
|
|
EventLoop::queue($failureCb);
|
2022-12-30 19:25:28 +01:00
|
|
|
$failureCb = null;
|
|
|
|
}
|
|
|
|
if ($token) {
|
2023-01-15 20:13:47 +01:00
|
|
|
if ($token->isRequested()) {
|
|
|
|
return null;
|
|
|
|
}
|
2023-01-11 18:47:27 +01:00
|
|
|
try {
|
|
|
|
delay($polling, true, $token);
|
|
|
|
} catch (CancelledException) {
|
2022-12-30 21:43:58 +01:00
|
|
|
return null;
|
2022-12-30 19:25:28 +01:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
delay($polling);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} while (!$result);
|
|
|
|
return static function () use (&$res): void {
|
|
|
|
if ($res) {
|
|
|
|
\flock($res, LOCK_UN);
|
|
|
|
\fclose($res);
|
|
|
|
$res = null;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
/**
|
|
|
|
* Asynchronously sleep.
|
|
|
|
*
|
|
|
|
* @param float $time Number of seconds to sleep for
|
|
|
|
*/
|
|
|
|
public static function sleep(float $time): void
|
|
|
|
{
|
|
|
|
delay($time);
|
|
|
|
}
|
|
|
|
/**
|
|
|
|
* Asynchronously read line.
|
|
|
|
*
|
|
|
|
* @param string $prompt Prompt
|
|
|
|
*/
|
2023-06-18 20:55:16 +02:00
|
|
|
public static function readLine(string $prompt = '', ?Cancellation $cancel = null): string
|
2022-12-30 19:25:28 +01:00
|
|
|
{
|
|
|
|
try {
|
|
|
|
Magic::togglePeriodicLogging();
|
|
|
|
$stdin = getStdin();
|
|
|
|
$stdout = getStdout();
|
|
|
|
if ($prompt) {
|
|
|
|
$stdout->write($prompt);
|
|
|
|
}
|
|
|
|
static $lines = [''];
|
2023-06-18 20:55:16 +02:00
|
|
|
while (\count($lines) < 2 && ($chunk = $stdin->read($cancel)) !== null) {
|
2022-12-30 19:25:28 +01:00
|
|
|
$chunk = \explode("\n", \str_replace(["\r", "\n\n"], "\n", $chunk));
|
|
|
|
$lines[\count($lines) - 1] .= \array_shift($chunk);
|
|
|
|
$lines = \array_merge($lines, $chunk);
|
|
|
|
}
|
|
|
|
} finally {
|
|
|
|
Magic::togglePeriodicLogging();
|
|
|
|
}
|
2023-03-17 11:30:40 +01:00
|
|
|
return \array_shift($lines) ?? '';
|
2022-12-30 19:25:28 +01:00
|
|
|
}
|
|
|
|
/**
|
|
|
|
* Asynchronously write to stdout/browser.
|
|
|
|
*
|
|
|
|
* @param string $string Message to echo
|
|
|
|
*/
|
2022-12-30 20:24:13 +01:00
|
|
|
public static function echo(string $string): void
|
2022-12-30 19:25:28 +01:00
|
|
|
{
|
2022-12-30 20:24:13 +01:00
|
|
|
getOutputBufferStream()->write($string);
|
2022-12-30 19:25:28 +01:00
|
|
|
}
|
|
|
|
}
|