From 68ab47de32afd5c68e1aaaa5f3a2eaf37db6fff2 Mon Sep 17 00:00:00 2001
From: Daniil Gentili
Date: Fri, 8 May 2020 11:53:47 +0200
Subject: [PATCH 01/29] Update bot.php
---
examples/bot.php | 14 +++-----------
1 file changed, 3 insertions(+), 11 deletions(-)
diff --git a/examples/bot.php b/examples/bot.php
index cc30c07d..af1c6425 100755
--- a/examples/bot.php
+++ b/examples/bot.php
@@ -82,17 +82,9 @@ class MyEventHandler extends EventHandler
return;
}
$res = \json_encode($update, JSON_PRETTY_PRINT);
- try {
- yield $this->messages->sendMessage(['peer' => $update, 'message' => "$res
", 'reply_to_msg_id' => isset($update['message']['id']) ? $update['message']['id'] : null, 'parse_mode' => 'HTML']);
- if (isset($update['message']['media']) && $update['message']['media']['_'] !== 'messageMediaGame') {
- yield $this->messages->sendMedia(['peer' => $update, 'message' => $update['message']['message'], 'media' => $update]);
- }
- } catch (RPCErrorException $e) {
- $this->report("Surfaced: $e");
- } catch (Exception $e) {
- if (\stripos($e->getMessage(), 'invalid constructor given') === false) {
- $this->report("Surfaced: $e");
- }
+ yield $this->messages->sendMessage(['peer' => $update, 'message' => "$res
", 'reply_to_msg_id' => isset($update['message']['id']) ? $update['message']['id'] : null, 'parse_mode' => 'HTML']);
+ if (isset($update['message']['media']) && $update['message']['media']['_'] !== 'messageMediaGame') {
+ yield $this->messages->sendMedia(['peer' => $update, 'message' => $update['message']['message'], 'media' => $update]);
}
}
}
From 83802fe944ab972c6a14506ea4fd5313974d47c8 Mon Sep 17 00:00:00 2001
From: Alexander Pankratov <34161928+xtrime-ru@users.noreply.github.com>
Date: Sun, 10 May 2020 20:17:15 +0300
Subject: [PATCH 02/29] Allow git modules install without ssh key (#810)
(cherry picked from commit 8cb18edc6ee12e8f8a47f6dc7ed9f3ea946bf043)
---
.gitmodules | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/.gitmodules b/.gitmodules
index 2aef1e0c..0816d508 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,12 +1,12 @@
[submodule "docs"]
path = docs
- url = git@github.com:danog/MadelineProtoDocs
+ url = https://github.com/danog/MadelineProtoDocs.git
[submodule "examples/magnaluna"]
path = examples/magnaluna
- url = git@github.com:danog/magnaluna
+ url = https://github.com/danog/magnaluna.git
[submodule "examples/pipesbot"]
path = examples/pipesbot
- url = git@github.com:danog/pipesbot
+ url = https://github.com/danog/pipesbot.git
[submodule "schemas"]
path = schemas
- url = git@github.com:danog/schemas
+ url = https://github.com/danog/schemas.git
From d5b2cbefd30f9103a885f7aa843f0035b30da3a0 Mon Sep 17 00:00:00 2001
From: Alexander Pankratov <34161928+xtrime-ru@users.noreply.github.com>
Date: Fri, 22 May 2020 13:30:14 +0300
Subject: [PATCH 03/29] Fix memory leaks when downloading files to callback.
(#816)
---
src/danog/MadelineProto/MTProto.php | 6 +-
.../MadelineProto/MTProtoTools/Files.php | 10 ++--
.../MTProtoTools/GarbageCollector.php | 59 +++++++++++++++++++
3 files changed, 69 insertions(+), 6 deletions(-)
create mode 100644 src/danog/MadelineProto/MTProtoTools/GarbageCollector.php
diff --git a/src/danog/MadelineProto/MTProto.php b/src/danog/MadelineProto/MTProto.php
index 256a17f5..4e7eb79d 100644
--- a/src/danog/MadelineProto/MTProto.php
+++ b/src/danog/MadelineProto/MTProto.php
@@ -22,12 +22,12 @@ namespace danog\MadelineProto;
use Amp\Dns\Resolver;
use Amp\File\StatCache;
use Amp\Http\Client\HttpClient;
-use Amp\Loop;
use danog\MadelineProto\Async\AsyncConstruct;
use danog\MadelineProto\Loop\Generic\PeriodicLoop;
use danog\MadelineProto\Loop\Update\FeedLoop;
use danog\MadelineProto\Loop\Update\SeqLoop;
use danog\MadelineProto\Loop\Update\UpdateLoop;
+use danog\MadelineProto\MTProtoTools\GarbageCollector;
use danog\MadelineProto\MTProtoTools\CombinedUpdatesState;
use danog\MadelineProto\MTProtoTools\MinDatabase;
use danog\MadelineProto\MTProtoTools\ReferenceDatabase;
@@ -465,6 +465,8 @@ class MTProto extends AsyncConstruct implements TLCallback
yield from $this->getConfig([], ['datacenter' => $this->datacenter->curdc]);
$this->startUpdateSystem(true);
$this->v = self::V;
+
+ GarbageCollector::start();
}
/**
* Sleep function.
@@ -921,6 +923,8 @@ class MTProto extends AsyncConstruct implements TLCallback
yield $this->updaters[false]->resume();
}
$this->updaters[false]->start();
+
+ GarbageCollector::start();
}
/**
* Unreference instance, allowing destruction.
diff --git a/src/danog/MadelineProto/MTProtoTools/Files.php b/src/danog/MadelineProto/MTProtoTools/Files.php
index 670eeb53..bf2e7a72 100644
--- a/src/danog/MadelineProto/MTProtoTools/Files.php
+++ b/src/danog/MadelineProto/MTProtoTools/Files.php
@@ -499,7 +499,7 @@ trait Files
$cb = [$bridge, 'callback'];
$read = $this->uploadFromCallable($reader, $size, $mime, '', $cb, true, $encrypted);
$write = $this->downloadToCallable($media, $writer, null, true, 0, -1, $chunk_size);
- list($res) = yield \danog\MadelineProto\Tools::all([$read, $write]);
+ [$res] = yield \danog\MadelineProto\Tools::all([$read, $write]);
return $res;
}
@@ -1038,7 +1038,7 @@ trait Files
if (\count($range) == 1) {
$range[1] = '';
}
- list($size_unit, $range_orig) = $range;
+ [$size_unit, $range_orig] = $range;
if ($size_unit == 'bytes') {
//multiple ranges could be specified at the same time, but for simplicity only serve the first range
//http://tools.ietf.org/id/draft-ietf-http-range-retrieval-00.txt
@@ -1046,7 +1046,7 @@ trait Files
if (\count($list) == 1) {
$list[1] = '';
}
- list($range, $extra_ranges) = $list;
+ [$range, $extra_ranges] = $list;
} else {
return [
'serve' => false,
@@ -1061,7 +1061,7 @@ trait Files
if (\count($listseek) == 1) {
$listseek[1] = '';
}
- list($seek_start, $seek_end) = $listseek;
+ [$seek_start, $seek_end] = $listseek;
$seek_end = empty($seek_end) ? ($messageMedia['size'] - 1) : \min(\abs(\intval($seek_end)), $messageMedia['size'] - 1);
@@ -1274,7 +1274,7 @@ trait Files
$time = 0;
$speed = 0;
$origCb = $cb;
- $cb = function () use ($cb, $count, &$time, &$speed) {
+ $cb = static function () use ($cb, $count, &$time, &$speed) {
static $cur = 0;
$cur++;
\danog\MadelineProto\Tools::callFork($cb($cur * 100 / $count, $time, $speed));
diff --git a/src/danog/MadelineProto/MTProtoTools/GarbageCollector.php b/src/danog/MadelineProto/MTProtoTools/GarbageCollector.php
new file mode 100644
index 00000000..55ee2a4d
--- /dev/null
+++ b/src/danog/MadelineProto/MTProtoTools/GarbageCollector.php
@@ -0,0 +1,59 @@
+ static::$memoryConsumption + static::$memoryDiffMb) {
+ gc_collect_cycles();
+ static::$memoryConsumption = static::getMemoryConsumption();
+ $cleanedMemory = $currentMemory - static::$memoryConsumption;
+ Logger::log("gc_collect_cycles done. Cleaned memory: $cleanedMemory Mb", Logger::NOTICE);
+ }
+ });
+ }
+
+ private static function getMemoryConsumption(): int
+ {
+ $memory = round(memory_get_usage()/1024/1024, 1);
+ Logger::log("Memory consumption: $memory Mb", Logger::VERBOSE);
+ return (int) $memory;
+ }
+}
\ No newline at end of file
From ddc81d87648fad3200434e75c910ef29c56e36f5 Mon Sep 17 00:00:00 2001
From: Alexander Pankratov <34161928+xtrime-ru@users.noreply.github.com>
Date: Sat, 23 May 2020 15:08:04 +0300
Subject: [PATCH 04/29] Allow downloadToResponse for media without size (#817)
---
.../MadelineProto/MTProtoTools/Files.php | 19 ++++++++++++-------
1 file changed, 12 insertions(+), 7 deletions(-)
diff --git a/src/danog/MadelineProto/MTProtoTools/Files.php b/src/danog/MadelineProto/MTProtoTools/Files.php
index bf2e7a72..70b9106c 100644
--- a/src/danog/MadelineProto/MTProtoTools/Files.php
+++ b/src/danog/MadelineProto/MTProtoTools/Files.php
@@ -996,7 +996,7 @@ trait Files
}
$response = new Response($result['code'], $result['headers'], $body);
- if ($result['serve']) {
+ if ($result['serve'] && !empty($result['headers']['Content-Length'])) {
$response->setHeader('content-length', $result['headers']['Content-Length']);
}
@@ -1063,7 +1063,8 @@ trait Files
}
[$seek_start, $seek_end] = $listseek;
- $seek_end = empty($seek_end) ? ($messageMedia['size'] - 1) : \min(\abs(\intval($seek_end)), $messageMedia['size'] - 1);
+ $size = $messageMedia['size'] ?? 0;
+ $seek_end = empty($seek_end) ? ($size - 1) : \min(\abs(\intval($seek_end)), $size - 1);
if (!empty($seek_start) && $seek_end < \abs(\intval($seek_start))) {
return [
@@ -1079,12 +1080,12 @@ trait Files
'code' => Status::OK,
'headers' => []
];
- if ($seek_start > 0 || $seek_end < $messageMedia['size'] - 1) {
+ if ($seek_start > 0 || $seek_end < $size - 1) {
$result['code'] = Status::PARTIAL_CONTENT;
- $result['headers']['Content-Range'] = "bytes ${seek_start}-${seek_end}/${messageMedia['size']}";
+ $result['headers']['Content-Range'] = "bytes ${seek_start}-${seek_end}/${$size}";
$result['headers']['Content-Length'] = $seek_end - $seek_start + 1;
- } else {
- $result['headers']['Content-Length'] = $messageMedia['size'];
+ } elseif ($size > 0) {
+ $result['headers']['Content-Length'] = $size;
}
$result['headers']['Content-Type'] = $messageMedia['mime'];
$result['headers']['Cache-Control'] = 'max-age=31556926';
@@ -1092,7 +1093,11 @@ trait Files
$result['headers']['Accept-Ranges'] = 'bytes';
if ($result['serve']) {
- $result['serve'] = [$seek_start, $seek_end + 1];
+ if ($seek_start === 0 && $seek_end === -1) {
+ $result['serve'] = [0, -1];
+ } else {
+ $result['serve'] = [$seek_start, $seek_end + 1];
+ }
}
return $result;
From 21750f8d6806d1d228be8453313f466c2b6fd420 Mon Sep 17 00:00:00 2001
From: Alexander Pankratov
Date: Sat, 23 May 2020 03:49:20 +0300
Subject: [PATCH 05/29] Allow downloadToResponse for media without size
---
src/danog/MadelineProto/MTProtoTools/Files.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/danog/MadelineProto/MTProtoTools/Files.php b/src/danog/MadelineProto/MTProtoTools/Files.php
index 70b9106c..a64d89fa 100644
--- a/src/danog/MadelineProto/MTProtoTools/Files.php
+++ b/src/danog/MadelineProto/MTProtoTools/Files.php
@@ -645,7 +645,7 @@ trait Files
*/
public function getPropicInfo($data): \Generator
{
- return yield from $this->getDownloadInfo($this->chats[(yield from $this->getInfo($data))['bot_api_id']]);
+ return yield from $this->getDownloadInfo(yield $this->chats[(yield from $this->getInfo($data))['bot_api_id']]);
}
/**
* Extract file info from bot API message.
From ff7d93f52d001b04fb90ee4431da75156de2932b Mon Sep 17 00:00:00 2001
From: Alexander Pankratov
Date: Sat, 25 Apr 2020 20:36:15 +0300
Subject: [PATCH 06/29] phpDoc fix
---
src/danog/MadelineProto/API.php | 4 ++--
src/danog/MadelineProto/MTProto.php | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/danog/MadelineProto/API.php b/src/danog/MadelineProto/API.php
index 0c1b7242..aa352bd6 100644
--- a/src/danog/MadelineProto/API.php
+++ b/src/danog/MadelineProto/API.php
@@ -41,7 +41,7 @@ class API extends InternalDoc
/**
* Instance of MadelineProto.
*
- * @var ?MTProto
+ * @var null|MTProto
*/
public $API;
@@ -66,7 +66,7 @@ class API extends InternalDoc
*
* @internal
*
- * @var ?MyTelegramOrgWrapper
+ * @var null|MyTelegramOrgWrapper
*/
private $myTelegramOrgWrapper;
diff --git a/src/danog/MadelineProto/MTProto.php b/src/danog/MadelineProto/MTProto.php
index 4e7eb79d..3a17eda4 100644
--- a/src/danog/MadelineProto/MTProto.php
+++ b/src/danog/MadelineProto/MTProto.php
@@ -206,7 +206,7 @@ class MTProto extends AsyncConstruct implements TLCallback
/**
* Instance of wrapper API.
*
- * @var ?APIWrapper
+ * @var null|APIWrapper
*/
public $wrapper;
/**
From ed493330a65ee1d0e956295f3d8830d5764c6fb8 Mon Sep 17 00:00:00 2001
From: Alexander Pankratov
Date: Sat, 25 Apr 2020 22:57:55 +0300
Subject: [PATCH 07/29] Database properties types
---
composer.json | 1 +
src/danog/MadelineProto/Db/DbArray.php | 11 +
.../MadelineProto/Db/DbPropertiesFabric.php | 49 ++
src/danog/MadelineProto/Db/DbType.php | 8 +
src/danog/MadelineProto/Db/MemoryArray.php | 15 +
src/danog/MadelineProto/Db/Mysql.php | 45 ++
src/danog/MadelineProto/Db/MysqlArray.php | 429 ++++++++++++++++++
.../MadelineProto/Db/SharedMemoryArray.php | 25 +
src/danog/MadelineProto/MTProto.php | 80 +++-
9 files changed, 650 insertions(+), 13 deletions(-)
create mode 100644 src/danog/MadelineProto/Db/DbArray.php
create mode 100644 src/danog/MadelineProto/Db/DbPropertiesFabric.php
create mode 100644 src/danog/MadelineProto/Db/DbType.php
create mode 100644 src/danog/MadelineProto/Db/MemoryArray.php
create mode 100644 src/danog/MadelineProto/Db/Mysql.php
create mode 100644 src/danog/MadelineProto/Db/MysqlArray.php
create mode 100644 src/danog/MadelineProto/Db/SharedMemoryArray.php
diff --git a/composer.json b/composer.json
index 3e5debca..0555de72 100644
--- a/composer.json
+++ b/composer.json
@@ -27,6 +27,7 @@
"amphp/dns": "^1",
"amphp/byte-stream": "^1",
"amphp/file": "^1",
+ "amphp/mysql": "^2.0",
"danog/dns-over-https": "^0.2",
"amphp/http-client-cookies": "^1",
"danog/tg-file-decoder": "^0.1",
diff --git a/src/danog/MadelineProto/Db/DbArray.php b/src/danog/MadelineProto/Db/DbArray.php
new file mode 100644
index 00000000..263d9a93
--- /dev/null
+++ b/src/danog/MadelineProto/Db/DbArray.php
@@ -0,0 +1,11 @@
+getArrayCopy();
+ }
+ return new static($value);
+ }
+}
\ No newline at end of file
diff --git a/src/danog/MadelineProto/Db/Mysql.php b/src/danog/MadelineProto/Db/Mysql.php
new file mode 100644
index 00000000..cada90af
--- /dev/null
+++ b/src/danog/MadelineProto/Db/Mysql.php
@@ -0,0 +1,45 @@
+ $this->table,
+ 'key' => $this->key,
+ 'settings' => $this->settings
+ ];
+ }
+
+ public function __unserialize($data): void
+ {
+ foreach ($data as $property => $value) {
+ $this->{$property} = $value;
+ }
+ $this->initDbConnection();
+ }
+
+ public static function getInstance(array $settings, string $name, $value = []): DbType
+ {
+ $instance = new static();
+ $instance->table = $name;
+ $instance->settings = $settings['mysql'];
+ $instance->initDbConnection();
+ $instance->prepareTable();
+
+ if (!empty($value) && !$value instanceof static) {
+ if ($value instanceof DbArray) {
+ $value = $value->getArrayCopy();
+ }
+ foreach ((array) $value as $key => $item) {
+ $instance[$key] = $item;
+ }
+ }
+
+
+ return $instance;
+ }
+
+ /**
+ * Check if offset exists
+ *
+ * @link https://php.net/manual/en/arrayiterator.offsetexists.php
+ *
+ * @param string $index
+ * The offset being checked.
+ *
+ *
+ * @return bool true if the offset exists, otherwise false
+ * @throws \Throwable
+ */
+ public function offsetExists($index)
+ {
+ $row = $this->syncRequest(
+ "SELECT count(`key`) as `count` FROM {$this->table} WHERE `key` = :index LIMIT 1",
+ ['index' => $index]
+ );
+
+ $row = reset($row);
+ return !empty($row['count']);
+ }
+
+ /**
+ * Get value for an offset
+ *
+ * @link https://php.net/manual/en/arrayiterator.offsetget.php
+ *
+ * @param string $index
+ * The offset to get the value from.
+ *
+ *
+ * @return mixed The value at offset index.
+ * @throws \Throwable
+ */
+ public function offsetGet($index)
+ {
+ $row = $this->syncRequest(
+ "SELECT `value` FROM {$this->table} WHERE `key` = :index LIMIT 1",
+ ['index' => $index]
+ );
+ $row = reset($row);
+ if ($row) {
+ return unserialize($row['value']);
+ }
+ return null;
+
+ }
+
+ /**
+ * Set value for an offset
+ *
+ * @link https://php.net/manual/en/arrayiterator.offsetset.php
+ *
+ * @param string $index
+ * The index to set for.
+ *
+ * @param $value
+ *
+ * @return void
+ * @throws \Throwable
+ */
+ public function offsetSet($index, $value)
+ {
+ $this->syncRequest("
+ INSERT INTO `{$this->table}`
+ SET `key` = :index, `value` = :value
+ ON DUPLICATE KEY UPDATE `value` = :value
+ ",
+ [
+ 'index' => $index,
+ 'value' => serialize($value),
+ ]
+ );
+ }
+
+ /**
+ * Unset value for an offset
+ *
+ * @link https://php.net/manual/en/arrayiterator.offsetunset.php
+ *
+ * @param string $index
+ * The offset to unset.
+ *
+ *
+ * @return void
+ * @throws \Throwable
+ */
+ public function offsetUnset($index)
+ {
+ $this->syncRequest("
+ DELETE FROM `{$this->table}`
+ WHERE `key` = :index
+ ",
+ ['index' => $index]
+ );
+ }
+
+ /**
+ * Append an element
+ * @link https://php.net/manual/en/arrayiterator.append.php
+ * @param mixed $value
+ * The value to append.
+ *
+ * @return void
+ */
+ public function append($value)
+ {
+ throw new \BadMethodCallException('Append operation does not supported');
+ }
+
+ /**
+ * Get array copy
+ *
+ * @link https://php.net/manual/en/arrayiterator.getarraycopy.php
+ * @return array A copy of the array, or array of public properties
+ * if ArrayIterator refers to an object.
+ * @throws \Throwable
+ */
+ public function getArrayCopy(): array
+ {
+ $rows = $this->syncRequest("SELECT `key`, `value` FROM {$this->table}");
+ $result = [];
+ foreach ($rows as $row) {
+ $result[$row['key']] = unserialize($row['value']);
+ }
+ return $result;
+ }
+
+ /**
+ * Count elements
+ *
+ * @link https://php.net/manual/en/arrayiterator.count.php
+ * @return int The number of elements or public properties in the associated
+ * array or object, respectively.
+ * @throws \Throwable
+ */
+ public function count(): int
+ {
+ return $this->syncRequest("SELECT count(`key`) as `count` FROM {$this->table}")['count'] ?? 0;
+ }
+
+ /**
+ * Sort array by values
+ * @link https://php.net/manual/en/arrayiterator.asort.php
+ * @return void
+ */
+ public function asort()
+ {
+ throw new \BadMethodCallException('Sort operation does not supported');
+ }
+
+ /**
+ * Sort array by keys
+ * @link https://php.net/manual/en/arrayiterator.ksort.php
+ * @return void
+ */
+ public function ksort()
+ {
+ throw new \BadMethodCallException('Sort operation does not supported');
+ }
+
+ /**
+ * User defined sort
+ * @link https://php.net/manual/en/arrayiterator.uasort.php
+ * @param string $cmp_function
+ * The compare function used for the sort.
+ *
+ * @return void
+ */
+ public function uasort($cmp_function)
+ {
+ throw new \BadMethodCallException('Sort operation does not supported');
+ }
+
+ /**
+ * User defined sort
+ * @link https://php.net/manual/en/arrayiterator.uksort.php
+ * @param string $cmp_function
+ * The compare function used for the sort.
+ *
+ * @return void
+ */
+ public function uksort($cmp_function)
+ {
+ throw new \BadMethodCallException('Sort operation does not supported');
+ }
+
+ /**
+ * Sort an array naturally
+ * @link https://php.net/manual/en/arrayiterator.natsort.php
+ * @return void
+ */
+ public function natsort()
+ {
+ throw new \BadMethodCallException('Sort operation does not supported');
+ }
+
+ /**
+ * Sort an array naturally, case insensitive
+ * @link https://php.net/manual/en/arrayiterator.natcasesort.php
+ * @return void
+ */
+ public function natcasesort()
+ {
+ throw new \BadMethodCallException('Sort operation does not supported');
+ }
+
+ /**
+ * Rewind array back to the start
+ *
+ * @link https://php.net/manual/en/arrayiterator.rewind.php
+ * @return void
+ * @throws \Throwable
+ */
+ public function rewind()
+ {
+ $this->key = null;
+ $this->key();
+ }
+
+ /**
+ * Return current array entry
+ *
+ * @link https://php.net/manual/en/arrayiterator.current.php
+ * @return mixed The current array entry.
+ * @throws \Throwable
+ */
+ public function current()
+ {
+ return $this->offsetGet($this->key());
+ }
+
+ /**
+ * Return current array key
+ *
+ * @link https://php.net/manual/en/arrayiterator.key.php
+ * @return string|float|int|bool|null The current array key.
+ * @throws \Throwable
+ */
+ public function key(): ?string
+ {
+ if ($this->key === null) {
+ $row = $this->syncRequest(
+ "SELECT `key` FROM {$this->table} ORDER BY `key` LIMIT 1"
+ );
+ if ($row) {
+ $row = reset($row);
+ $this->key = $row['key'] ?? null;
+ }
+
+ }
+ return $this->key;
+ }
+
+ /**
+ * Move to next entry
+ *
+ * @link https://php.net/manual/en/arrayiterator.next.php
+ * @return void
+ * @throws \Throwable
+ */
+ public function next() {
+ $row = $this->syncRequest(
+ "SELECT `key` FROM {$this->table} WHERE `key` > :key LIMIT 1",
+ ['key' => $this->key()]
+ );
+ $row = reset($row);
+ $this->key = $row['key'] ?? null;
+ }
+
+ /**
+ * Check whether array contains more entries
+ *
+ * @link https://php.net/manual/en/arrayiterator.valid.php
+ * @return bool
+ * @throws \Throwable
+ */
+ public function valid() {
+ if ($this->key() === null) {
+ return false;
+ }
+
+ $row = $this->syncRequest(
+ "SELECT `key` FROM {$this->table} WHERE `key` > :key LIMIT 1",
+ ['key' => $this->key()]
+ );
+
+ return $row !== null;
+ }
+
+ /**
+ * Seek to position
+ * @link https://php.net/manual/en/arrayiterator.seek.php
+ * @param int $position
+ * The position to seek to.
+ *
+ * @return void
+ */
+ public function seek($position)
+ {
+ $row = $this->syncRequest(
+ "SELECT `key` FROM {$this->table} ORDER BY `key` LIMIT 1, :position",
+ ['offset' => $position]
+ );
+ $row = reset($row);
+ if (isset($row['key'])) {
+ $this->key = $row['key'];
+ }
+ }
+
+ private function initDbConnection()
+ {
+ $this->db = Mysql::getConnection(
+ $this->settings['host'],
+ $this->settings['port'],
+ $this->settings['user'],
+ $this->settings['password'],
+ $this->settings['database'],
+ );
+ }
+
+ /**
+ * Create table for property
+ *
+ * @return array|null
+ * @throws \Throwable
+ */
+ private function prepareTable()
+ {
+ return $this->syncRequest("
+ CREATE TABLE IF NOT EXISTS `{$this->table}`
+ (
+ `key` VARCHAR(255) NOT NULL,
+ `value` LONGTEXT NULL,
+ `ts` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (`key`)
+ )
+ ");
+ }
+
+ /**
+ * Perform blocking request to db
+ *
+ * @param string $query
+ * @param array $params
+ *
+ * @return array|null
+ * @throws \Throwable
+ */
+ private function syncRequest(string $query, array $params = []): array
+ {
+ return Tools::wait(
+ call(
+ function() use($query, $params) {
+ $request = yield $this->db->execute($query, $params);
+ $result = [];
+ if ($request instanceof ResultSet) {
+ while (yield $request->advance()) {
+ $row = $request->getCurrent();
+ if (isset($row['key'])) {
+ $result[$row['key']] = $row;
+ } else {
+ $result[] = $row;
+ }
+
+ }
+ }
+ return $result;
+ }
+ )
+ );
+ }
+
+}
\ No newline at end of file
diff --git a/src/danog/MadelineProto/Db/SharedMemoryArray.php b/src/danog/MadelineProto/Db/SharedMemoryArray.php
new file mode 100644
index 00000000..2252556c
--- /dev/null
+++ b/src/danog/MadelineProto/Db/SharedMemoryArray.php
@@ -0,0 +1,25 @@
+getArrayCopy();
+ }
+ $value = array_replace_recursive(static::$instance->getArrayCopy(), (array) $value);
+ foreach ($value as $key => $item) {
+ static::$instance[$key] = $item;
+ }
+ }
+
+ return static::$instance;
+ }
+}
\ No newline at end of file
diff --git a/src/danog/MadelineProto/MTProto.php b/src/danog/MadelineProto/MTProto.php
index 3a17eda4..a6c43204 100644
--- a/src/danog/MadelineProto/MTProto.php
+++ b/src/danog/MadelineProto/MTProto.php
@@ -23,6 +23,12 @@ use Amp\Dns\Resolver;
use Amp\File\StatCache;
use Amp\Http\Client\HttpClient;
use danog\MadelineProto\Async\AsyncConstruct;
+use danog\MadelineProto\Db\DbArray;
+use danog\MadelineProto\Db\DbType;
+use danog\MadelineProto\Db\Engines\DbInterface;
+use danog\MadelineProto\Db\DbPropertiesFabric;
+use danog\MadelineProto\Db\Mysql;
+use danog\MadelineProto\Db\Types\ArrayType;
use danog\MadelineProto\Loop\Generic\PeriodicLoop;
use danog\MadelineProto\Loop\Update\FeedLoop;
use danog\MadelineProto\Loop\Update\SeqLoop;
@@ -85,7 +91,7 @@ class MTProto extends AsyncConstruct implements TLCallback
*
* @var int
*/
- const V = 138;
+ const V = 139;
/**
* String release version.
*
@@ -278,15 +284,15 @@ class MTProto extends AsyncConstruct implements TLCallback
/**
* Internal peer database.
*
- * @var array
+ * @var DbArray
*/
- public $chats = [];
+ public $chats;
/**
* Cached parameters for fetching channel participants.
*
- * @var array
+ * @var DbArray
*/
- public $channel_participants = [];
+ public $channel_participants;
/**
* When we last stored data in remote peer database (now doesn't exist anymore).
*
@@ -302,9 +308,9 @@ class MTProto extends AsyncConstruct implements TLCallback
/**
* Full chat info database.
*
- * @var array
+ * @var DbArray
*/
- public $full_chats = [];
+ public $full_chats;
/**
* Latest chat message ID map for update handling.
*
@@ -407,6 +413,18 @@ class MTProto extends AsyncConstruct implements TLCallback
* @var \danog\MadelineProto\TL\TL
*/
private $TL;
+
+ /**
+ * List of properties stored in database (memory or external)
+ * @see DbPropertiesFabric
+ * @var array
+ */
+ private array $dbProperies = [
+ 'chats' => 'array',
+ 'full_chats' => 'array',
+ 'channel_participants' => 'array'
+ ];
+
/**
* Constructor function.
*
@@ -540,6 +558,18 @@ class MTProto extends AsyncConstruct implements TLCallback
'reportDest'
];
}
+
+ public function initDb(bool $reset = false): void
+ {
+ foreach ($this->dbProperies as $property => $type) {
+ if ($reset) {
+ unset($this->{$property});
+ } else {
+ $this->{$property} = DbPropertiesFabric::get($this->settings['db'], $type, $property, $this->{$property});
+ }
+ }
+ }
+
/**
* Cleanup memory and session file.
*
@@ -751,6 +781,9 @@ class MTProto extends AsyncConstruct implements TLCallback
}
$this->TL->init($this->settings['tl_schema']['src'], $callbacks);
}
+
+ $this->initDb();
+
}
/**
* Upgrade MadelineProto instance.
@@ -777,9 +810,9 @@ class MTProto extends AsyncConstruct implements TLCallback
if (isset($settings['authorization']['rsa_key'])) {
unset($settings['authorization']['rsa_key']);
}
- if (!isset($this->full_chats)) {
- $this->full_chats = [];
- }
+
+ $this->initDb();
+
if (!isset($this->secret_chats)) {
$this->secret_chats = [];
}
@@ -799,6 +832,8 @@ class MTProto extends AsyncConstruct implements TLCallback
$chat['mtproto'] = 1;
}
}
+ unset($chat);
+
foreach ($settings['connection_settings'] as $key => &$connection) {
if (\in_array($key, ['default_dc', 'media_socket_count', 'robin_period'])) {
continue;
@@ -821,6 +856,8 @@ class MTProto extends AsyncConstruct implements TLCallback
$connection['obfuscated'] = true;
}
}
+ unset($connection);
+
$this->resetMTProtoSession(true, true);
$this->config = ['expires' => -1];
$this->dh_config = ['version' => 0];
@@ -1244,6 +1281,23 @@ class MTProto extends AsyncConstruct implements TLCallback
'run_callback' => true,
], 'secret_chats' => ['accept_chats' => true],
'serialization' => ['serialization_interval' => 30, 'cleanup_before_serialization' => false],
+ /**
+ * Where internal database will be stored?
+ * memory - session file
+ * sharedMemory - multiples instances share db if run in single process
+ * mysql - mysql database, shared by all instances in all processes.
+ */
+ 'db' => [
+ 'type' => 'memory',
+ /** @see Mysql */
+ 'mysql' => [
+ 'host' => '127.0.0.1',
+ 'port' => 3306,
+ 'user' => 'root',
+ 'password' => '',
+ 'database' => 'MadelineProto'
+ ]
+ ],
'upload' => ['allow_automatic_upload' => true, 'part_size' => 512 * 1024, 'parallel_chunks' => 20], 'download' => ['report_broken_media' => true, 'part_size' => 1024 * 1024, 'parallel_chunks' => 20], 'pwr' => [
'pwr' => false,
// Need info ?
@@ -1469,13 +1523,13 @@ class MTProto extends AsyncConstruct implements TLCallback
$this->authorization = null;
$this->updates = [];
$this->secret_chats = [];
- $this->chats = [];
- $this->users = [];
+
+ $this->initDb(true);
+
$this->tos = ['expires' => 0, 'accepted' => true];
$this->referenceDatabase = new ReferenceDatabase($this);
$this->minDatabase = new MinDatabase($this);
$this->dialog_params = ['_' => 'MadelineProto.dialogParams', 'limit' => 0, 'offset_date' => 0, 'offset_id' => 0, 'offset_peer' => ['_' => 'inputPeerEmpty'], 'count' => 0];
- $this->full_chats = [];
}
/**
* Reset the update state and fetch all updates from the beginning.
From 0d191f4157fddc8af6db87309de40b10f88406b1 Mon Sep 17 00:00:00 2001
From: Alexander Pankratov
Date: Mon, 27 Apr 2020 03:21:18 +0300
Subject: [PATCH 08/29] Bugfixes and optimizations
---
src/danog/MadelineProto/Db/DbArray.php | 7 +-
src/danog/MadelineProto/Db/MemoryArray.php | 6 +-
src/danog/MadelineProto/Db/Mysql.php | 8 +-
src/danog/MadelineProto/Db/MysqlArray.php | 155 ++++--------------
.../MadelineProto/Db/SharedMemoryArray.php | 7 +-
src/danog/MadelineProto/MTProto.php | 18 +-
.../MTProtoTools/PeerHandler.php | 18 +-
.../MTProtoTools/ReferenceDatabase.php | 4 +-
.../MTProtoTools/UpdateHandler.php | 4 +-
.../MadelineProto/Wrappers/DialogHandler.php | 7 +-
10 files changed, 97 insertions(+), 137 deletions(-)
diff --git a/src/danog/MadelineProto/Db/DbArray.php b/src/danog/MadelineProto/Db/DbArray.php
index 263d9a93..631bb302 100644
--- a/src/danog/MadelineProto/Db/DbArray.php
+++ b/src/danog/MadelineProto/Db/DbArray.php
@@ -2,10 +2,7 @@
namespace danog\MadelineProto\Db;
-abstract class DbArray extends \ArrayIterator implements DbType
+interface DbArray extends DbType, \ArrayAccess, \Countable, \Iterator, \SeekableIterator
{
- protected function __construct($array = [], $flags = 0)
- {
- parent::__construct((array) $array, $flags | self::STD_PROP_LIST);
- }
+ public function getArrayCopy();
}
\ No newline at end of file
diff --git a/src/danog/MadelineProto/Db/MemoryArray.php b/src/danog/MadelineProto/Db/MemoryArray.php
index 6cab009b..658edc56 100644
--- a/src/danog/MadelineProto/Db/MemoryArray.php
+++ b/src/danog/MadelineProto/Db/MemoryArray.php
@@ -2,8 +2,12 @@
namespace danog\MadelineProto\Db;
-class MemoryArray extends DbArray
+class MemoryArray extends \ArrayIterator implements DbArray
{
+ protected function __construct($array = [], $flags = 0)
+ {
+ parent::__construct((array) $array, $flags | self::STD_PROP_LIST);
+ }
static function getInstance(array $settings, string $name, $value = []): DbArray
{
diff --git a/src/danog/MadelineProto/Db/Mysql.php b/src/danog/MadelineProto/Db/Mysql.php
index cada90af..4374d71e 100644
--- a/src/danog/MadelineProto/Db/Mysql.php
+++ b/src/danog/MadelineProto/Db/Mysql.php
@@ -2,7 +2,6 @@
namespace danog\MadelineProto\Db;
-use Amp\Loop;
use Amp\Mysql\ConnectionConfig;
use Amp\Mysql\Pool;
use function Amp\Mysql\Pool;
@@ -42,4 +41,11 @@ class Mysql
return static::$connections[$dbKey];
}
+ public function __destruct()
+ {
+ foreach (static::$connections as $connection) {
+ $connection->close();
+ }
+ }
+
}
\ No newline at end of file
diff --git a/src/danog/MadelineProto/Db/MysqlArray.php b/src/danog/MadelineProto/Db/MysqlArray.php
index 83ec3023..0bb89057 100644
--- a/src/danog/MadelineProto/Db/MysqlArray.php
+++ b/src/danog/MadelineProto/Db/MysqlArray.php
@@ -7,18 +7,18 @@ use Amp\Sql\ResultSet;
use danog\MadelineProto\Tools;
use function Amp\call;
-class MysqlArray extends DbArray
+class MysqlArray implements DbArray
{
private string $table;
private array $settings;
private Pool $db;
private ?string $key = null;
+ private $current;
public function __serialize(): array
{
return [
'table' => $this->table,
- 'key' => $this->key,
'settings' => $this->settings
];
}
@@ -71,8 +71,7 @@ class MysqlArray extends DbArray
['index' => $index]
);
- $row = reset($row);
- return !empty($row['count']);
+ return !empty($row[0]['count']);
}
/**
@@ -93,11 +92,7 @@ class MysqlArray extends DbArray
"SELECT `value` FROM {$this->table} WHERE `key` = :index LIMIT 1",
['index' => $index]
);
- $row = reset($row);
- if ($row) {
- return unserialize($row['value']);
- }
- return null;
+ return $this->getValue($row);
}
@@ -150,19 +145,6 @@ class MysqlArray extends DbArray
);
}
- /**
- * Append an element
- * @link https://php.net/manual/en/arrayiterator.append.php
- * @param mixed $value
- * The value to append.
- *
- * @return void
- */
- public function append($value)
- {
- throw new \BadMethodCallException('Append operation does not supported');
- }
-
/**
* Get array copy
*
@@ -178,6 +160,7 @@ class MysqlArray extends DbArray
foreach ($rows as $row) {
$result[$row['key']] = unserialize($row['value']);
}
+
return $result;
}
@@ -191,73 +174,8 @@ class MysqlArray extends DbArray
*/
public function count(): int
{
- return $this->syncRequest("SELECT count(`key`) as `count` FROM {$this->table}")['count'] ?? 0;
- }
-
- /**
- * Sort array by values
- * @link https://php.net/manual/en/arrayiterator.asort.php
- * @return void
- */
- public function asort()
- {
- throw new \BadMethodCallException('Sort operation does not supported');
- }
-
- /**
- * Sort array by keys
- * @link https://php.net/manual/en/arrayiterator.ksort.php
- * @return void
- */
- public function ksort()
- {
- throw new \BadMethodCallException('Sort operation does not supported');
- }
-
- /**
- * User defined sort
- * @link https://php.net/manual/en/arrayiterator.uasort.php
- * @param string $cmp_function
- * The compare function used for the sort.
- *
- * @return void
- */
- public function uasort($cmp_function)
- {
- throw new \BadMethodCallException('Sort operation does not supported');
- }
-
- /**
- * User defined sort
- * @link https://php.net/manual/en/arrayiterator.uksort.php
- * @param string $cmp_function
- * The compare function used for the sort.
- *
- * @return void
- */
- public function uksort($cmp_function)
- {
- throw new \BadMethodCallException('Sort operation does not supported');
- }
-
- /**
- * Sort an array naturally
- * @link https://php.net/manual/en/arrayiterator.natsort.php
- * @return void
- */
- public function natsort()
- {
- throw new \BadMethodCallException('Sort operation does not supported');
- }
-
- /**
- * Sort an array naturally, case insensitive
- * @link https://php.net/manual/en/arrayiterator.natcasesort.php
- * @return void
- */
- public function natcasesort()
- {
- throw new \BadMethodCallException('Sort operation does not supported');
+ $row = $this->syncRequest("SELECT count(`key`) as `count` FROM {$this->table}");
+ return $row[0]['count'] ?? 0;
}
/**
@@ -271,6 +189,7 @@ class MysqlArray extends DbArray
{
$this->key = null;
$this->key();
+ $this->current = null;
}
/**
@@ -282,7 +201,16 @@ class MysqlArray extends DbArray
*/
public function current()
{
- return $this->offsetGet($this->key());
+ return $this->current ?: $this->offsetGet($this->key());
+ }
+
+ private function getValue(array $row)
+ {
+ if ($row) {
+ $row = reset($row);
+ return unserialize($row['value']);
+ }
+ return null;
}
/**
@@ -298,11 +226,7 @@ class MysqlArray extends DbArray
$row = $this->syncRequest(
"SELECT `key` FROM {$this->table} ORDER BY `key` LIMIT 1"
);
- if ($row) {
- $row = reset($row);
- $this->key = $row['key'] ?? null;
- }
-
+ $this->key = $row[0]['key'] ?? null;
}
return $this->key;
}
@@ -314,13 +238,15 @@ class MysqlArray extends DbArray
* @return void
* @throws \Throwable
*/
- public function next() {
+ public function next()
+ {
$row = $this->syncRequest(
- "SELECT `key` FROM {$this->table} WHERE `key` > :key LIMIT 1",
+ "SELECT `key`, `value` FROM {$this->table} WHERE `key` > :key ORDER BY `key` LIMIT 1",
['key' => $this->key()]
);
- $row = reset($row);
- $this->key = $row['key'] ?? null;
+
+ $this->key = $row[0]['key'] ?? null;
+ $this->current = $this->getValue($row);
}
/**
@@ -330,17 +256,9 @@ class MysqlArray extends DbArray
* @return bool
* @throws \Throwable
*/
- public function valid() {
- if ($this->key() === null) {
- return false;
- }
-
- $row = $this->syncRequest(
- "SELECT `key` FROM {$this->table} WHERE `key` > :key LIMIT 1",
- ['key' => $this->key()]
- );
-
- return $row !== null;
+ public function valid():bool
+ {
+ return $this->key !== null;
}
/**
@@ -357,14 +275,12 @@ class MysqlArray extends DbArray
"SELECT `key` FROM {$this->table} ORDER BY `key` LIMIT 1, :position",
['offset' => $position]
);
- $row = reset($row);
- if (isset($row['key'])) {
- $this->key = $row['key'];
- }
+ $this->key = $row[0]['key'] ?? $this->key;
}
private function initDbConnection()
{
+ //TODO Use MtProto::$settings
$this->db = Mysql::getConnection(
$this->settings['host'],
$this->settings['port'],
@@ -386,7 +302,7 @@ class MysqlArray extends DbArray
CREATE TABLE IF NOT EXISTS `{$this->table}`
(
`key` VARCHAR(255) NOT NULL,
- `value` LONGTEXT NULL,
+ `value` MEDIUMBLOB NULL,
`ts` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`key`)
)
@@ -411,13 +327,7 @@ class MysqlArray extends DbArray
$result = [];
if ($request instanceof ResultSet) {
while (yield $request->advance()) {
- $row = $request->getCurrent();
- if (isset($row['key'])) {
- $result[$row['key']] = $row;
- } else {
- $result[] = $row;
- }
-
+ $result[] = $request->getCurrent();
}
}
return $result;
@@ -425,5 +335,4 @@ class MysqlArray extends DbArray
)
);
}
-
}
\ No newline at end of file
diff --git a/src/danog/MadelineProto/Db/SharedMemoryArray.php b/src/danog/MadelineProto/Db/SharedMemoryArray.php
index 2252556c..2d82c1dc 100644
--- a/src/danog/MadelineProto/Db/SharedMemoryArray.php
+++ b/src/danog/MadelineProto/Db/SharedMemoryArray.php
@@ -2,10 +2,15 @@
namespace danog\MadelineProto\Db;
-class SharedMemoryArray extends DbArray
+class SharedMemoryArray extends \ArrayIterator implements DbArray
{
private static SharedMemoryArray $instance;
+ protected function __construct($array = [], $flags = 0)
+ {
+ parent::__construct((array) $array, $flags | self::STD_PROP_LIST);
+ }
+
public static function getInstance(array $settings, string $name, $value = []): DbArray
{
if (empty(static::$instance)) {
diff --git a/src/danog/MadelineProto/MTProto.php b/src/danog/MadelineProto/MTProto.php
index a6c43204..c738173f 100644
--- a/src/danog/MadelineProto/MTProto.php
+++ b/src/danog/MadelineProto/MTProto.php
@@ -24,7 +24,6 @@ use Amp\File\StatCache;
use Amp\Http\Client\HttpClient;
use danog\MadelineProto\Async\AsyncConstruct;
use danog\MadelineProto\Db\DbArray;
-use danog\MadelineProto\Db\DbType;
use danog\MadelineProto\Db\Engines\DbInterface;
use danog\MadelineProto\Db\DbPropertiesFabric;
use danog\MadelineProto\Db\Mysql;
@@ -422,7 +421,12 @@ class MTProto extends AsyncConstruct implements TLCallback
private array $dbProperies = [
'chats' => 'array',
'full_chats' => 'array',
- 'channel_participants' => 'array'
+ 'channel_participants' => 'array',
+ 'caching_simple' => 'array',
+ 'caching_simple_username' => 'array',
+ 'caching_possible_username' => 'array',
+ 'caching_full_info' => 'array',
+ 'caching_username_id' => 'array',
];
/**
@@ -568,6 +572,16 @@ class MTProto extends AsyncConstruct implements TLCallback
$this->{$property} = DbPropertiesFabric::get($this->settings['db'], $type, $property, $this->{$property});
}
}
+
+ if (!$reset && count($this->caching_username_id) === 0) {
+ $this->logger('Filling database cache. This can take few minutes.', Logger::WARNING);
+ foreach ($this->chats as $id => $chat) {
+ if (isset($chat['username'])) {
+ $this->caching_username_id[$chat['username']] = $id;
+ }
+ }
+ $this->logger('Cache filled.', Logger::WARNING);
+ }
}
/**
diff --git a/src/danog/MadelineProto/MTProtoTools/PeerHandler.php b/src/danog/MadelineProto/MTProtoTools/PeerHandler.php
index debf2f8f..cdb395e4 100644
--- a/src/danog/MadelineProto/MTProtoTools/PeerHandler.php
+++ b/src/danog/MadelineProto/MTProtoTools/PeerHandler.php
@@ -34,6 +34,8 @@ trait PeerHandler
public $caching_simple_username = [];
public $caching_possible_username = [];
public $caching_full_info = [];
+ public $caching_username_id = [];
+
/**
* Convert MTProto channel ID to bot API channel ID.
*
@@ -562,7 +564,19 @@ trait PeerHandler
}
return yield from $this->getInfo($this->supportUser);
}
+ if ($bot_api_id = $this->caching_username_id[$id] ?? null) {
+ $chat = $this->chats[$bot_api_id];
+ if (empty($chat['username']) || $chat['username'] !== $id) {
+ unset($this->caching_username_id[$id]);
+ } else {
+ return $this->genAll($this->chats[$bot_api_id], $folder_id);
+ }
+ }
+
foreach ($this->chats as $bot_api_id => $chat) {
+ if (isset($chat['username'])) {
+ $this->caching_username_id[$id] = $bot_api_id;
+ }
if (isset($chat['username']) && \strtolower($chat['username']) === $id) {
if ($chat['min'] ?? false && !isset($this->caching_full_info[$bot_api_id])) {
$this->caching_full_info[$bot_api_id] = true;
@@ -969,7 +983,9 @@ trait PeerHandler
}
\sort($ids, SORT_NUMERIC);
$gres['hash'] = \danog\MadelineProto\Tools::genVectorHash($ids);
- $this->channel_participants[$channel['channel_id']][$filter][$q][$offset][$limit] = $gres;
+ $participant = $this->channel_participants[$channel['channel_id']];
+ $participant[$filter][$q][$offset][$limit] = $gres;
+ $this->channel_participants[$channel['channel_id']] = $participant;
}
private function getParticipantsHash($channel, $filter, $q, $offset, $limit)
{
diff --git a/src/danog/MadelineProto/MTProtoTools/ReferenceDatabase.php b/src/danog/MadelineProto/MTProtoTools/ReferenceDatabase.php
index 236fb4f9..72dd357b 100644
--- a/src/danog/MadelineProto/MTProtoTools/ReferenceDatabase.php
+++ b/src/danog/MadelineProto/MTProtoTools/ReferenceDatabase.php
@@ -423,7 +423,9 @@ class ReferenceDatabase implements TLCallback
// Peer + photo ID
case self::PEER_PHOTO_ORIGIN:
if (isset($this->API->full_chats[$origin['peer']]['last_update'])) {
- $this->API->full_chats[$origin['peer']]['last_update'] = 0;
+ $chat = $this->API->full_chats[$origin['peer']];
+ $chat['last_update'] = 0;
+ $this->API->full_chats[$origin['peer']] = $chat;
}
$this->API->getFullInfo($origin['peer']);
break;
diff --git a/src/danog/MadelineProto/MTProtoTools/UpdateHandler.php b/src/danog/MadelineProto/MTProtoTools/UpdateHandler.php
index 67424f42..facb7475 100644
--- a/src/danog/MadelineProto/MTProtoTools/UpdateHandler.php
+++ b/src/danog/MadelineProto/MTProtoTools/UpdateHandler.php
@@ -332,7 +332,9 @@ trait UpdateHandler
}
if (\in_array($update['_'], ['updateUserName', 'updateUserPhone', 'updateUserBlocked', 'updateUserPhoto', 'updateContactRegistered', 'updateContactLink'])) {
$id = $this->getId($update);
- $this->full_chats[$id]['last_update'] = 0;
+ $chat = $this->full_chats[$id];
+ $chat['last_update'] = 0;
+ $this->full_chats[$id] = $chat;
yield from $this->getFullInfo($id);
}
if ($update['_'] === 'updateDcOptions') {
diff --git a/src/danog/MadelineProto/Wrappers/DialogHandler.php b/src/danog/MadelineProto/Wrappers/DialogHandler.php
index c9d80767..d7de6417 100644
--- a/src/danog/MadelineProto/Wrappers/DialogHandler.php
+++ b/src/danog/MadelineProto/Wrappers/DialogHandler.php
@@ -33,7 +33,12 @@ trait DialogHandler
if ($this->authorization['user']['bot']) {
$res = [];
foreach ($this->chats as $chat) {
- $res[] = $this->genAll($chat)['Peer'];
+ try {
+ $res[] = $this->genAll($chat)['Peer'];
+ } catch (\Throwable $e) {
+ continue;
+ }
+
}
return $res;
}
From 26abf9f04e895e89a804049f934bf7bed5757011 Mon Sep 17 00:00:00 2001
From: Alexander Pankratov
Date: Tue, 28 Apr 2020 03:41:06 +0300
Subject: [PATCH 09/29] Bugfixes and optimizations
---
src/danog/MadelineProto/Db/DbArray.php | 6 +
.../MadelineProto/Db/DbPropertiesFabric.php | 16 +-
src/danog/MadelineProto/Db/DbType.php | 2 +-
src/danog/MadelineProto/Db/MemoryArray.php | 30 ++-
src/danog/MadelineProto/Db/Mysql.php | 7 -
src/danog/MadelineProto/Db/MysqlArray.php | 174 ++++++++++++++----
.../MadelineProto/Db/SharedMemoryArray.php | 30 ---
src/danog/MadelineProto/MTProto.php | 39 ++--
.../MTProtoTools/PeerHandler.php | 26 +--
9 files changed, 216 insertions(+), 114 deletions(-)
delete mode 100644 src/danog/MadelineProto/Db/SharedMemoryArray.php
diff --git a/src/danog/MadelineProto/Db/DbArray.php b/src/danog/MadelineProto/Db/DbArray.php
index 631bb302..27b7e538 100644
--- a/src/danog/MadelineProto/Db/DbArray.php
+++ b/src/danog/MadelineProto/Db/DbArray.php
@@ -2,7 +2,13 @@
namespace danog\MadelineProto\Db;
+use Amp\Producer;
+use Amp\Promise;
+
interface DbArray extends DbType, \ArrayAccess, \Countable, \Iterator, \SeekableIterator
{
public function getArrayCopy();
+ public function offsetGetAsync(string $offset): Promise;
+ public function offsetSetAsync(string $offset, $value): Promise;
+ public function getIterator(): Producer;
}
\ No newline at end of file
diff --git a/src/danog/MadelineProto/Db/DbPropertiesFabric.php b/src/danog/MadelineProto/Db/DbPropertiesFabric.php
index 8ac5754b..6739ce3e 100644
--- a/src/danog/MadelineProto/Db/DbPropertiesFabric.php
+++ b/src/danog/MadelineProto/Db/DbPropertiesFabric.php
@@ -2,10 +2,13 @@
namespace danog\MadelineProto\Db;
+use danog\MadelineProto\API;
+use danog\MadelineProto\MTProto;
+
class DbPropertiesFabric
{
/**
- * @param array $dbSettings
+ * @param MTProto $madelineProto
* @param string $propertyType
* @param string $name
* @param $value
@@ -16,16 +19,14 @@ class DbPropertiesFabric
* @uses \danog\MadelineProto\Db\SharedMemoryArray
* @uses \danog\MadelineProto\Db\MysqlArray
*/
- public static function get(array $dbSettings, string $propertyType, string $name, $value = null): DbType
+ public static function get(MTProto $madelineProto, string $propertyType, string $name, $value = null): DbType
{
$class = __NAMESPACE__;
+ $dbSettings = $madelineProto->settings['db'];
switch (strtolower($dbSettings['type'])) {
case 'memory':
$class .= '\Memory';
break;
- case 'sharedmemory':
- $class .= '\SharedMemory';
- break;
case 'mysql':
$class .= '\Mysql';
break;
@@ -34,6 +35,7 @@ class DbPropertiesFabric
}
+ /** @var DbType $class */
switch (strtolower($propertyType)){
case 'array':
$class .= 'Array';
@@ -42,8 +44,8 @@ class DbPropertiesFabric
throw new \InvalidArgumentException("Unknown $propertyType: {$propertyType}");
}
- /** @var DbType $class */
- return $class::getInstance($dbSettings, $name, $value);
+ $prefix = (string) ($madelineProto->getSelf()['id'] ?? 'tmp');
+ return $class::getInstance($name, $value, $prefix, $dbSettings[$dbSettings['type']]??[]);
}
}
\ No newline at end of file
diff --git a/src/danog/MadelineProto/Db/DbType.php b/src/danog/MadelineProto/Db/DbType.php
index e9bf5dcc..bab9d584 100644
--- a/src/danog/MadelineProto/Db/DbType.php
+++ b/src/danog/MadelineProto/Db/DbType.php
@@ -4,5 +4,5 @@ namespace danog\MadelineProto\Db;
interface DbType
{
- static function getInstance(array $settings, string $name, $value): self;
+ static function getInstance(string $name, $value, string $tablePrefix, array $settings): self;
}
\ No newline at end of file
diff --git a/src/danog/MadelineProto/Db/MemoryArray.php b/src/danog/MadelineProto/Db/MemoryArray.php
index 658edc56..39d94ac1 100644
--- a/src/danog/MadelineProto/Db/MemoryArray.php
+++ b/src/danog/MadelineProto/Db/MemoryArray.php
@@ -2,6 +2,10 @@
namespace danog\MadelineProto\Db;
+use Amp\Producer;
+use Amp\Promise;
+use function Amp\call;
+
class MemoryArray extends \ArrayIterator implements DbArray
{
protected function __construct($array = [], $flags = 0)
@@ -9,11 +13,35 @@ class MemoryArray extends \ArrayIterator implements DbArray
parent::__construct((array) $array, $flags | self::STD_PROP_LIST);
}
- static function getInstance(array $settings, string $name, $value = []): DbArray
+ public static function getInstance(string $name, $value, string $tablePrefix, array $settings): DbArray
{
if ($value instanceof DbArray) {
$value = $value->getArrayCopy();
}
return new static($value);
}
+
+ public static function getDbConnection(array $settings)
+ {
+ return null;
+ }
+
+ public function offsetGetAsync(string $offset): Promise
+ {
+ return call(fn() => $this->offsetGet($offset));
+ }
+
+ public function offsetSetAsync(string $offset, $value): Promise
+ {
+ return call(fn() => $this->offsetSet($offset, $value));
+ }
+
+ public function getIterator(): Producer
+ {
+ return new Producer(function (callable $emit) {
+ foreach ($this as $value) {
+ yield $emit($value);
+ }
+ });
+ }
}
\ No newline at end of file
diff --git a/src/danog/MadelineProto/Db/Mysql.php b/src/danog/MadelineProto/Db/Mysql.php
index 4374d71e..569cebe0 100644
--- a/src/danog/MadelineProto/Db/Mysql.php
+++ b/src/danog/MadelineProto/Db/Mysql.php
@@ -41,11 +41,4 @@ class Mysql
return static::$connections[$dbKey];
}
- public function __destruct()
- {
- foreach (static::$connections as $connection) {
- $connection->close();
- }
- }
-
}
\ No newline at end of file
diff --git a/src/danog/MadelineProto/Db/MysqlArray.php b/src/danog/MadelineProto/Db/MysqlArray.php
index 0bb89057..d9763efc 100644
--- a/src/danog/MadelineProto/Db/MysqlArray.php
+++ b/src/danog/MadelineProto/Db/MysqlArray.php
@@ -2,10 +2,15 @@
namespace danog\MadelineProto\Db;
+use Amp\Loop;
use Amp\Mysql\Pool;
+use Amp\Producer;
+use Amp\Promise;
use Amp\Sql\ResultSet;
+use danog\MadelineProto\Logger;
use danog\MadelineProto\Tools;
use function Amp\call;
+use function Amp\Promise\wait;
class MysqlArray implements DbArray
{
@@ -28,25 +33,51 @@ class MysqlArray implements DbArray
foreach ($data as $property => $value) {
$this->{$property} = $value;
}
- $this->initDbConnection();
+ try {
+ $this->db = static::getDbConnection($this->settings);
+ } catch (\Throwable $e) {
+ Logger::log($e->getMessage(), Logger::ERROR);
+ }
+
}
- public static function getInstance(array $settings, string $name, $value = []): DbType
+ public static function getInstance(string $name, $value, string $tablePrefix, array $settings): DbType
{
$instance = new static();
- $instance->table = $name;
- $instance->settings = $settings['mysql'];
- $instance->initDbConnection();
- $instance->prepareTable();
- if (!empty($value) && !$value instanceof static) {
- if ($value instanceof DbArray) {
- $value = $value->getArrayCopy();
- }
- foreach ((array) $value as $key => $item) {
- $instance[$key] = $item;
+ $instance->table = "{$tablePrefix}_{$name}";
+ $instance->settings = $settings;
+ $instance->db = static::getDbConnection($settings);
+
+ if ($value instanceof static) {
+ if ($instance->table !== $value->table) {
+ $instance->renameTable($value->table, $instance->table);
}
}
+ $instance->prepareTable();
+
+ Loop::defer(function() use($value, $instance){
+ if (!empty($value) && !$value instanceof static) {
+ Logger::log('Converting database.', Logger::ERROR);
+ if ($value instanceof DbArray) {
+ $value = $value->getArrayCopy();
+ }
+ $value = (array) $value;
+ $counter = 0;
+ $total = count($value);
+ foreach ((array) $value as $key => $item) {
+ $counter++;
+ if ($counter % 100 === 0) {
+ yield $instance->offsetSetAsync($key, $item);
+ Logger::log("Converting database. $counter/$total", Logger::WARNING);
+ } else {
+ $instance->offsetSetAsync($key, $item);
+ }
+
+ }
+ Logger::log('Converting database done.', Logger::ERROR);
+ }
+ });
return $instance;
@@ -88,12 +119,19 @@ class MysqlArray implements DbArray
*/
public function offsetGet($index)
{
- $row = $this->syncRequest(
- "SELECT `value` FROM {$this->table} WHERE `key` = :index LIMIT 1",
- ['index' => $index]
- );
- return $this->getValue($row);
+ return wait($this->offsetGetAsync($index));
+ }
+
+ public function offsetGetAsync(string $offset): Promise
+ {
+ return call(function() use($offset) {
+ $row = yield $this->request(
+ "SELECT `value` FROM {$this->table} WHERE `key` = :index LIMIT 1",
+ ['index' => $offset]
+ );
+ return $this->getValue($row);
+ });
}
/**
@@ -123,6 +161,20 @@ class MysqlArray implements DbArray
);
}
+ public function offsetSetAsync($index, $value): Promise
+ {
+ return $this->request("
+ INSERT INTO `{$this->table}`
+ SET `key` = :index, `value` = :value
+ ON DUPLICATE KEY UPDATE `value` = :value
+ ",
+ [
+ 'index' => $index,
+ 'value' => serialize($value),
+ ]
+ );
+ }
+
/**
* Unset value for an offset
*
@@ -164,6 +216,19 @@ class MysqlArray implements DbArray
return $result;
}
+ public function getIterator(): Producer
+ {
+ return new Producer(function (callable $emit) {
+ $request = yield $this->db->execute("SELECT `key`, `value` FROM {$this->table}");
+
+ while (yield $request->advance()) {
+ $row = $request->getCurrent();
+
+ yield $emit($this->getValue($row));
+ }
+ });
+ }
+
/**
* Count elements
*
@@ -207,7 +272,9 @@ class MysqlArray implements DbArray
private function getValue(array $row)
{
if ($row) {
- $row = reset($row);
+ if (!empty($row[0]['value'])) {
+ $row = reset($row);
+ }
return unserialize($row['value']);
}
return null;
@@ -272,21 +339,20 @@ class MysqlArray implements DbArray
public function seek($position)
{
$row = $this->syncRequest(
- "SELECT `key` FROM {$this->table} ORDER BY `key` LIMIT 1, :position",
+ "SELECT `key` FROM {$this->table} ORDER BY `key` LIMIT 1 OFFSET :position",
['offset' => $position]
);
$this->key = $row[0]['key'] ?? $this->key;
}
- private function initDbConnection()
+ public static function getDbConnection(array $settings): Pool
{
- //TODO Use MtProto::$settings
- $this->db = Mysql::getConnection(
- $this->settings['host'],
- $this->settings['port'],
- $this->settings['user'],
- $this->settings['password'],
- $this->settings['database'],
+ return Mysql::getConnection(
+ $settings['host'],
+ $settings['port'],
+ $settings['user'],
+ $settings['password'],
+ $settings['database'],
);
}
@@ -309,6 +375,18 @@ class MysqlArray implements DbArray
");
}
+ private function renameTable(string $from, string $to)
+ {
+ try {
+ $this->syncRequest("
+ ALTER TABLE {$from} RENAME TO {$to};
+ ");
+ } catch (\Throwable $e) {
+ Logger::log("Cant rename table {$from} to {$to}", Logger::WARNING);
+ }
+
+ }
+
/**
* Perform blocking request to db
*
@@ -320,19 +398,35 @@ class MysqlArray implements DbArray
*/
private function syncRequest(string $query, array $params = []): array
{
- return Tools::wait(
- call(
- function() use($query, $params) {
- $request = yield $this->db->execute($query, $params);
- $result = [];
- if ($request instanceof ResultSet) {
- while (yield $request->advance()) {
- $result[] = $request->getCurrent();
- }
- }
- return $result;
+ return Tools::wait($this->request($query, $params));
+ }
+
+ /**
+ * Perform blocking request to db
+ *
+ * @param string $query
+ * @param array $params
+ *
+ * @return Promise
+ * @throws \Throwable
+ */
+ private function request(string $query, array $params = []): Promise
+ {
+ return call(function() use($query, $params) {
+ if (empty($this->db)) {
+ return [];
+ }
+
+ $request = yield $this->db->execute($query, $params);
+ $result = [];
+ if ($request instanceof ResultSet) {
+ while (yield $request->advance()) {
+ $result[] = $request->getCurrent();
}
- )
- );
+ }
+ return $result;
+ });
+
+
}
}
\ No newline at end of file
diff --git a/src/danog/MadelineProto/Db/SharedMemoryArray.php b/src/danog/MadelineProto/Db/SharedMemoryArray.php
deleted file mode 100644
index 2d82c1dc..00000000
--- a/src/danog/MadelineProto/Db/SharedMemoryArray.php
+++ /dev/null
@@ -1,30 +0,0 @@
-getArrayCopy();
- }
- $value = array_replace_recursive(static::$instance->getArrayCopy(), (array) $value);
- foreach ($value as $key => $item) {
- static::$instance[$key] = $item;
- }
- }
-
- return static::$instance;
- }
-}
\ No newline at end of file
diff --git a/src/danog/MadelineProto/MTProto.php b/src/danog/MadelineProto/MTProto.php
index c738173f..45aa8362 100644
--- a/src/danog/MadelineProto/MTProto.php
+++ b/src/danog/MadelineProto/MTProto.php
@@ -24,10 +24,8 @@ use Amp\File\StatCache;
use Amp\Http\Client\HttpClient;
use danog\MadelineProto\Async\AsyncConstruct;
use danog\MadelineProto\Db\DbArray;
-use danog\MadelineProto\Db\Engines\DbInterface;
use danog\MadelineProto\Db\DbPropertiesFabric;
use danog\MadelineProto\Db\Mysql;
-use danog\MadelineProto\Db\Types\ArrayType;
use danog\MadelineProto\Loop\Generic\PeriodicLoop;
use danog\MadelineProto\Loop\Update\FeedLoop;
use danog\MadelineProto\Loop\Update\SeqLoop;
@@ -286,6 +284,13 @@ class MTProto extends AsyncConstruct implements TLCallback
* @var DbArray
*/
public $chats;
+
+ /**
+ * Cache of usernames for chats
+ *
+ * @var DbArray
+ */
+ public $usernames;
/**
* Cached parameters for fetching channel participants.
*
@@ -422,11 +427,7 @@ class MTProto extends AsyncConstruct implements TLCallback
'chats' => 'array',
'full_chats' => 'array',
'channel_participants' => 'array',
- 'caching_simple' => 'array',
- 'caching_simple_username' => 'array',
- 'caching_possible_username' => 'array',
- 'caching_full_info' => 'array',
- 'caching_username_id' => 'array',
+ 'usernames' => 'array',
];
/**
@@ -507,6 +508,7 @@ class MTProto extends AsyncConstruct implements TLCallback
'referenceDatabase',
'minDatabase',
'channel_participants',
+ 'usernames',
// Misc caching
'dialog_params',
@@ -569,18 +571,22 @@ class MTProto extends AsyncConstruct implements TLCallback
if ($reset) {
unset($this->{$property});
} else {
- $this->{$property} = DbPropertiesFabric::get($this->settings['db'], $type, $property, $this->{$property});
+ $this->{$property} = DbPropertiesFabric::get($this, $type, $property, $this->{$property});
}
}
- if (!$reset && count($this->caching_username_id) === 0) {
- $this->logger('Filling database cache. This can take few minutes.', Logger::WARNING);
- foreach ($this->chats as $id => $chat) {
- if (isset($chat['username'])) {
- $this->caching_username_id[$chat['username']] = $id;
+ if (!$reset && count($this->usernames) === 0) {
+ \Amp\Loop::run(function() {
+ $this->logger('Filling database cache. This can take few minutes.', Logger::WARNING);
+ $iterator = $this->chats->getIterator();
+ while (yield $iterator->advance()) {
+ $chat = $iterator->getCurrent();
+ if (isset($chat['username'])) {
+ $this->usernames->offsetSetAsync(\strtolower($chat['username']), $this->getId($chat));
+ }
}
- }
- $this->logger('Cache filled.', Logger::WARNING);
+ $this->logger('Cache filled.', Logger::WARNING);
+ });
}
}
@@ -1298,8 +1304,7 @@ class MTProto extends AsyncConstruct implements TLCallback
/**
* Where internal database will be stored?
* memory - session file
- * sharedMemory - multiples instances share db if run in single process
- * mysql - mysql database, shared by all instances in all processes.
+ * mysql - mysql database
*/
'db' => [
'type' => 'memory',
diff --git a/src/danog/MadelineProto/MTProtoTools/PeerHandler.php b/src/danog/MadelineProto/MTProtoTools/PeerHandler.php
index cdb395e4..31f7f0e6 100644
--- a/src/danog/MadelineProto/MTProtoTools/PeerHandler.php
+++ b/src/danog/MadelineProto/MTProtoTools/PeerHandler.php
@@ -34,7 +34,6 @@ trait PeerHandler
public $caching_simple_username = [];
public $caching_possible_username = [];
public $caching_full_info = [];
- public $caching_username_id = [];
/**
* Convert MTProto channel ID to bot API channel ID.
@@ -122,6 +121,7 @@ trait PeerHandler
}
}
$this->chats[$user['id']] = $user;
+ $this->cacheChatUsername($user['id'], $user);
$this->cachePwrChat($user['id'], false, true);
}
break;
@@ -149,6 +149,7 @@ trait PeerHandler
if (!isset($this->chats[-$chat['id']]) || $this->chats[-$chat['id']] !== $chat) {
$this->logger->logger("Updated chat -{$chat['id']}", \danog\MadelineProto\Logger::ULTRA_VERBOSE);
$this->chats[-$chat['id']] = $chat;
+ $this->cacheChatUsername(-$chat['id'], $chat);
$this->cachePwrChat(-$chat['id'], $this->settings['peer']['full_fetch'], true);
}
break;
@@ -185,6 +186,7 @@ trait PeerHandler
$chat = $newchat;
}
$this->chats[$bot_api_id] = $chat;
+ $this->cacheChatUsername($bot_api_id, $chat);
if ($this->settings['peer']['full_fetch'] && (!isset($this->full_chats[$bot_api_id]) || $this->full_chats[$bot_api_id]['full']['participants_count'] !== (yield from $this->getFullInfo($bot_api_id))['full']['participants_count'])) {
$this->cachePwrChat($bot_api_id, $this->settings['peer']['full_fetch'], true);
}
@@ -192,6 +194,14 @@ trait PeerHandler
break;
}
}
+
+ private function cacheChatUsername(int $id, array $chat)
+ {
+ if (!empty($chat['username'])) {
+ $this->usernames[strtolower($chat['username'])] = $id;
+ }
+ }
+
private function cachePwrChat($id, $full_fetch, $send)
{
\danog\MadelineProto\Tools::callFork((function () use ($id, $full_fetch, $send): \Generator {
@@ -564,19 +574,12 @@ trait PeerHandler
}
return yield from $this->getInfo($this->supportUser);
}
- if ($bot_api_id = $this->caching_username_id[$id] ?? null) {
+ if ($bot_api_id = $this->usernames[$id] ?? null) {
$chat = $this->chats[$bot_api_id];
- if (empty($chat['username']) || $chat['username'] !== $id) {
- unset($this->caching_username_id[$id]);
- } else {
- return $this->genAll($this->chats[$bot_api_id], $folder_id);
+ if (empty($chat['username']) || \strtolower($chat['username']) !== $id) {
+ unset($this->usernames[$id]);
}
- }
- foreach ($this->chats as $bot_api_id => $chat) {
- if (isset($chat['username'])) {
- $this->caching_username_id[$id] = $bot_api_id;
- }
if (isset($chat['username']) && \strtolower($chat['username']) === $id) {
if ($chat['min'] ?? false && !isset($this->caching_full_info[$bot_api_id])) {
$this->caching_full_info[$bot_api_id] = true;
@@ -598,6 +601,7 @@ trait PeerHandler
return $this->genAll($this->chats[$bot_api_id], $folder_id);
}
}
+
if ($recursive) {
yield from $this->resolveUsername($id);
return yield from $this->getInfo($id, false);
From a883684f05f093ba48a62432aa28e47facdce6dc Mon Sep 17 00:00:00 2001
From: Alexander Pankratov
Date: Sat, 2 May 2020 20:36:59 +0300
Subject: [PATCH 10/29] Cache for Mysql Data Provider
---
.../MadelineProto/Db/ArrayCacheTrait.php | 76 +++++++++++++++++++
src/danog/MadelineProto/Db/Mysql.php | 50 ++++++++----
src/danog/MadelineProto/Db/MysqlArray.php | 41 +++++-----
src/danog/MadelineProto/MTProto.php | 3 +-
4 files changed, 133 insertions(+), 37 deletions(-)
create mode 100644 src/danog/MadelineProto/Db/ArrayCacheTrait.php
diff --git a/src/danog/MadelineProto/Db/ArrayCacheTrait.php b/src/danog/MadelineProto/Db/ArrayCacheTrait.php
new file mode 100644
index 00000000..27bb3f40
--- /dev/null
+++ b/src/danog/MadelineProto/Db/ArrayCacheTrait.php
@@ -0,0 +1,76 @@
+ mixed,
+ * 'ttl' => int
+ * ],
+ * ...
+ * ]
+ * @var array
+ */
+ protected array $cache = [];
+ protected string $ttl = '+1 day';
+ private string $ttlCheckInterval = '+1 second';
+ private int $nextTtlCheckTs = 0;
+
+ protected function getCache(string $key, $default = null)
+ {
+ if ($cacheItem = $this->cache[$key] ?? null) {
+ $this->cache[$key]['ttl'] = strtotime($this->ttl);
+ } else {
+ return $default;
+ }
+
+ return $cacheItem['value'];
+ }
+
+ /**
+ * Save item in cache
+ *
+ * @param string $key
+ * @param $value
+ */
+ protected function setCache(string $key, $value): void
+ {
+ $now = time();
+ $this->cache[$key] = [
+ 'value' => $value,
+ 'ttl' => $now,
+ ];
+
+ if ($this->nextTtlCheckTs < $now) {
+ $this->nextTtlCheckTs = strtotime($this->ttlCheckInterval, $now);
+ foreach ($this->cache as $cacheKey => $cacheValue) {
+ if ($cacheValue['ttl'] < $now) {
+ $this->unsetCache($cacheKey);
+ }
+ }
+ }
+ }
+
+ /**
+ * Remove key from cache
+ *
+ * @param string $key
+ */
+ protected function unsetCache(string $key): void
+ {
+ unset($this->cache[$key]);
+ }
+
+ /**
+ * Remove all keys from cache
+ */
+ protected function clearCache(): void
+ {
+ $this->cache = [];
+ }
+
+}
\ No newline at end of file
diff --git a/src/danog/MadelineProto/Db/Mysql.php b/src/danog/MadelineProto/Db/Mysql.php
index 569cebe0..7f7f60cd 100644
--- a/src/danog/MadelineProto/Db/Mysql.php
+++ b/src/danog/MadelineProto/Db/Mysql.php
@@ -5,26 +5,25 @@ namespace danog\MadelineProto\Db;
use Amp\Mysql\ConnectionConfig;
use Amp\Mysql\Pool;
use function Amp\Mysql\Pool;
+use function Amp\Promise\wait;
class Mysql
{
/** @var Pool[] */
private static array $connections;
- private static function connect(
- string $host = '127.0.0.1',
- int $port = 3306,
- string $user = 'root',
- string $password = '',
- string $db = 'MadelineProto'
- ) {
- $config = ConnectionConfig::fromString(
- "host={$host} port={$port} user={$user} password={$password} db={$db}"
- );
-
- return Pool($config);
- }
-
+ /**
+ * @param string $host
+ * @param int $port
+ * @param string $user
+ * @param string $password
+ * @param string $db
+ *
+ * @return Pool
+ * @throws \Amp\Sql\ConnectionException
+ * @throws \Amp\Sql\FailureException
+ * @throws \Throwable
+ */
public static function getConnection(
string $host = '127.0.0.1',
int $port = 3306,
@@ -35,10 +34,31 @@ class Mysql
{
$dbKey = "$host:$port:$db";
if (empty(static::$connections[$dbKey])) {
- static::$connections[$dbKey] = static::connect($host, $port, $user, $password, $db);
+ $config = ConnectionConfig::fromString(
+ "host={$host} port={$port} user={$user} password={$password} db={$db}"
+ );
+
+ static::createDb($config);
+ static::$connections[$dbKey] = pool($config);
}
return static::$connections[$dbKey];
}
+ /**
+ * @param ConnectionConfig $config
+ *
+ * @throws \Amp\Sql\ConnectionException
+ * @throws \Amp\Sql\FailureException
+ * @throws \Throwable
+ */
+ private static function createDb(ConnectionConfig $config) {
+ $db = $config->getDatabase();
+ wait(pool($config->withDatabase(null))->query("
+ CREATE DATABASE IF NOT EXISTS `{$db}`
+ CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci
+ "));
+
+ }
+
}
\ No newline at end of file
diff --git a/src/danog/MadelineProto/Db/MysqlArray.php b/src/danog/MadelineProto/Db/MysqlArray.php
index d9763efc..eccdc090 100644
--- a/src/danog/MadelineProto/Db/MysqlArray.php
+++ b/src/danog/MadelineProto/Db/MysqlArray.php
@@ -8,12 +8,13 @@ use Amp\Producer;
use Amp\Promise;
use Amp\Sql\ResultSet;
use danog\MadelineProto\Logger;
-use danog\MadelineProto\Tools;
use function Amp\call;
use function Amp\Promise\wait;
class MysqlArray implements DbArray
{
+ use ArrayCacheTrait;
+
private string $table;
private array $settings;
private Pool $db;
@@ -48,6 +49,7 @@ class MysqlArray implements DbArray
$instance->table = "{$tablePrefix}_{$name}";
$instance->settings = $settings;
$instance->db = static::getDbConnection($settings);
+ $instance->ttl = $settings['cache_ttl'] ?? $instance->ttl;
if ($value instanceof static) {
if ($instance->table !== $value->table) {
@@ -97,12 +99,7 @@ class MysqlArray implements DbArray
*/
public function offsetExists($index)
{
- $row = $this->syncRequest(
- "SELECT count(`key`) as `count` FROM {$this->table} WHERE `key` = :index LIMIT 1",
- ['index' => $index]
- );
-
- return !empty($row[0]['count']);
+ return $this->offsetGet($index) !== null;
}
/**
@@ -126,11 +123,20 @@ class MysqlArray implements DbArray
public function offsetGetAsync(string $offset): Promise
{
return call(function() use($offset) {
+ if ($cached = $this->getCache($offset)) {
+ return $cached;
+ }
+
$row = yield $this->request(
"SELECT `value` FROM {$this->table} WHERE `key` = :index LIMIT 1",
['index' => $offset]
);
- return $this->getValue($row);
+
+ if ($value = $this->getValue($row)) {
+ $this->setCache($offset, $value);
+ }
+
+ return $value;
});
}
@@ -149,20 +155,13 @@ class MysqlArray implements DbArray
*/
public function offsetSet($index, $value)
{
- $this->syncRequest("
- INSERT INTO `{$this->table}`
- SET `key` = :index, `value` = :value
- ON DUPLICATE KEY UPDATE `value` = :value
- ",
- [
- 'index' => $index,
- 'value' => serialize($value),
- ]
- );
+ wait($this->offsetSetAsync($index, $value));
}
public function offsetSetAsync($index, $value): Promise
{
+ $this->setCache($index, $value);
+
return $this->request("
INSERT INTO `{$this->table}`
SET `key` = :index, `value` = :value
@@ -189,6 +188,8 @@ class MysqlArray implements DbArray
*/
public function offsetUnset($index)
{
+ $this->unsetCache($index);
+
$this->syncRequest("
DELETE FROM `{$this->table}`
WHERE `key` = :index
@@ -398,7 +399,7 @@ class MysqlArray implements DbArray
*/
private function syncRequest(string $query, array $params = []): array
{
- return Tools::wait($this->request($query, $params));
+ return wait($this->request($query, $params));
}
/**
@@ -426,7 +427,5 @@ class MysqlArray implements DbArray
}
return $result;
});
-
-
}
}
\ No newline at end of file
diff --git a/src/danog/MadelineProto/MTProto.php b/src/danog/MadelineProto/MTProto.php
index 45aa8362..313bfc18 100644
--- a/src/danog/MadelineProto/MTProto.php
+++ b/src/danog/MadelineProto/MTProto.php
@@ -1314,7 +1314,8 @@ class MTProto extends AsyncConstruct implements TLCallback
'port' => 3306,
'user' => 'root',
'password' => '',
- 'database' => 'MadelineProto'
+ 'database' => 'MadelineProto',
+ 'cache_ttl' => '+1 day',
]
],
'upload' => ['allow_automatic_upload' => true, 'part_size' => 512 * 1024, 'parallel_chunks' => 20], 'download' => ['report_broken_media' => true, 'part_size' => 1024 * 1024, 'parallel_chunks' => 20], 'pwr' => [
From 46f7e63734cc1248c38d52eb24c3e9a027845cf7 Mon Sep 17 00:00:00 2001
From: Alexander Pankratov
Date: Sun, 3 May 2020 04:33:54 +0300
Subject: [PATCH 11/29] Async refactor
---
src/danog/MadelineProto/Db/DbArray.php | 11 +-
.../MadelineProto/Db/DbPropertiesFabric.php | 15 +-
src/danog/MadelineProto/Db/DbType.php | 2 +-
src/danog/MadelineProto/Db/MemoryArray.php | 28 ++-
src/danog/MadelineProto/Db/Mysql.php | 18 +-
src/danog/MadelineProto/Db/MysqlArray.php | 168 ++++--------------
src/danog/MadelineProto/InternalDoc.php | 13 +-
src/danog/MadelineProto/MTProto.php | 52 +++---
.../MTProtoTools/PeerHandler.php | 100 ++++++-----
.../MTProtoTools/ReferenceDatabase.php | 8 +-
.../MTProtoTools/UpdateHandler.php | 2 +-
.../MadelineProto/Wrappers/DialogHandler.php | 6 +-
src/danog/MadelineProto/Wrappers/Login.php | 2 +-
13 files changed, 188 insertions(+), 237 deletions(-)
diff --git a/src/danog/MadelineProto/Db/DbArray.php b/src/danog/MadelineProto/Db/DbArray.php
index 27b7e538..edfaf2b3 100644
--- a/src/danog/MadelineProto/Db/DbArray.php
+++ b/src/danog/MadelineProto/Db/DbArray.php
@@ -5,10 +5,13 @@ namespace danog\MadelineProto\Db;
use Amp\Producer;
use Amp\Promise;
-interface DbArray extends DbType, \ArrayAccess, \Countable, \Iterator, \SeekableIterator
+interface DbArray extends DbType, \ArrayAccess, \Countable
{
- public function getArrayCopy();
- public function offsetGetAsync(string $offset): Promise;
- public function offsetSetAsync(string $offset, $value): Promise;
+ public function getArrayCopy(): array;
+ public function offsetExists($offset): Promise;
+ public function offsetGet($offset): Promise;
+ public function offsetSet($offset, $value);
+ public function offsetUnset($offset): Promise;
+ public function count(): Promise;
public function getIterator(): Producer;
}
\ No newline at end of file
diff --git a/src/danog/MadelineProto/Db/DbPropertiesFabric.php b/src/danog/MadelineProto/Db/DbPropertiesFabric.php
index 6739ce3e..cc2dce8c 100644
--- a/src/danog/MadelineProto/Db/DbPropertiesFabric.php
+++ b/src/danog/MadelineProto/Db/DbPropertiesFabric.php
@@ -2,7 +2,6 @@
namespace danog\MadelineProto\Db;
-use danog\MadelineProto\API;
use danog\MadelineProto\MTProto;
class DbPropertiesFabric
@@ -13,7 +12,7 @@ class DbPropertiesFabric
* @param string $name
* @param $value
*
- * @return mixed
+ * @return DbType
*
* @uses \danog\MadelineProto\Db\MemoryArray
* @uses \danog\MadelineProto\Db\SharedMemoryArray
@@ -44,8 +43,18 @@ class DbPropertiesFabric
throw new \InvalidArgumentException("Unknown $propertyType: {$propertyType}");
}
- $prefix = (string) ($madelineProto->getSelf()['id'] ?? 'tmp');
+ $prefix = static::getSessionId($madelineProto);
return $class::getInstance($name, $value, $prefix, $dbSettings[$dbSettings['type']]??[]);
}
+ private static function getSessionId(MTProto $madelineProto): string
+ {
+ $result = $madelineProto->getSelf()['id'] ?? null;
+ if (!$result) {
+ $result = 'tmp_';
+ $result .= str_replace('0','', spl_object_hash($madelineProto));
+ }
+ return (string) $result;
+ }
+
}
\ No newline at end of file
diff --git a/src/danog/MadelineProto/Db/DbType.php b/src/danog/MadelineProto/Db/DbType.php
index bab9d584..f816a778 100644
--- a/src/danog/MadelineProto/Db/DbType.php
+++ b/src/danog/MadelineProto/Db/DbType.php
@@ -4,5 +4,5 @@ namespace danog\MadelineProto\Db;
interface DbType
{
- static function getInstance(string $name, $value, string $tablePrefix, array $settings): self;
+ static function getInstance(string $name, $value = null, string $tablePrefix = '', array $settings = []): self;
}
\ No newline at end of file
diff --git a/src/danog/MadelineProto/Db/MemoryArray.php b/src/danog/MadelineProto/Db/MemoryArray.php
index 39d94ac1..18132189 100644
--- a/src/danog/MadelineProto/Db/MemoryArray.php
+++ b/src/danog/MadelineProto/Db/MemoryArray.php
@@ -13,7 +13,7 @@ class MemoryArray extends \ArrayIterator implements DbArray
parent::__construct((array) $array, $flags | self::STD_PROP_LIST);
}
- public static function getInstance(string $name, $value, string $tablePrefix, array $settings): DbArray
+ public static function getInstance(string $name, $value = null, string $tablePrefix = '', array $settings = []): DbArray
{
if ($value instanceof DbArray) {
$value = $value->getArrayCopy();
@@ -21,26 +21,36 @@ class MemoryArray extends \ArrayIterator implements DbArray
return new static($value);
}
- public static function getDbConnection(array $settings)
+ public function offsetExists($offset): Promise
{
- return null;
+ return call(fn() => parent::offsetExists($offset));
}
- public function offsetGetAsync(string $offset): Promise
+ public function offsetGet($offset): Promise
{
- return call(fn() => $this->offsetGet($offset));
+ return call(fn() => parent::offsetGet($offset));
}
- public function offsetSetAsync(string $offset, $value): Promise
+ public function offsetUnset($offset): Promise
{
- return call(fn() => $this->offsetSet($offset, $value));
+ return call(fn() => parent::offsetUnset($offset));
+ }
+
+ public function count(): Promise
+ {
+ return call(fn() => parent::count());
+ }
+
+ public function getArrayCopy(): array
+ {
+ return parent::getArrayCopy();
}
public function getIterator(): Producer
{
return new Producer(function (callable $emit) {
- foreach ($this as $value) {
- yield $emit($value);
+ foreach ($this as $key => $value) {
+ yield $emit([$key, $value]);
}
});
}
diff --git a/src/danog/MadelineProto/Db/Mysql.php b/src/danog/MadelineProto/Db/Mysql.php
index 7f7f60cd..07c9051b 100644
--- a/src/danog/MadelineProto/Db/Mysql.php
+++ b/src/danog/MadelineProto/Db/Mysql.php
@@ -4,6 +4,7 @@ namespace danog\MadelineProto\Db;
use Amp\Mysql\ConnectionConfig;
use Amp\Mysql\Pool;
+use function Amp\call;
use function Amp\Mysql\Pool;
use function Amp\Promise\wait;
@@ -52,12 +53,17 @@ class Mysql
* @throws \Amp\Sql\FailureException
* @throws \Throwable
*/
- private static function createDb(ConnectionConfig $config) {
- $db = $config->getDatabase();
- wait(pool($config->withDatabase(null))->query("
- CREATE DATABASE IF NOT EXISTS `{$db}`
- CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci
- "));
+ private static function createDb(ConnectionConfig $config)
+ {
+ wait(call(function() use($config) {
+ $db = $config->getDatabase();
+ $connection = pool($config->withDatabase(null));
+ yield $connection->query("
+ CREATE DATABASE IF NOT EXISTS `{$db}`
+ CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci
+ ");
+ $connection->close();
+ }));
}
diff --git a/src/danog/MadelineProto/Db/MysqlArray.php b/src/danog/MadelineProto/Db/MysqlArray.php
index eccdc090..cbb3583e 100644
--- a/src/danog/MadelineProto/Db/MysqlArray.php
+++ b/src/danog/MadelineProto/Db/MysqlArray.php
@@ -18,8 +18,6 @@ class MysqlArray implements DbArray
private string $table;
private array $settings;
private Pool $db;
- private ?string $key = null;
- private $current;
public function __serialize(): array
{
@@ -42,7 +40,7 @@ class MysqlArray implements DbArray
}
- public static function getInstance(string $name, $value, string $tablePrefix, array $settings): DbType
+ public static function getInstance(string $name, $value = null, string $tablePrefix = '', array $settings = []): DbType
{
$instance = new static();
@@ -52,13 +50,18 @@ class MysqlArray implements DbArray
$instance->ttl = $settings['cache_ttl'] ?? $instance->ttl;
if ($value instanceof static) {
- if ($instance->table !== $value->table) {
+ if (
+ mb_strpos($value->table, 'tmp') === 0 &&
+ mb_strpos($instance->table, 'tmp') !== 0
+ ) {
$instance->renameTable($value->table, $instance->table);
+ } elseif (mb_strpos($instance->table, 'tmp') === 0){
+ $instance->table = $value->table;
}
}
$instance->prepareTable();
- Loop::defer(function() use($value, $instance){
+ Loop::defer(static function() use($value, $instance) {
if (!empty($value) && !$value instanceof static) {
Logger::log('Converting database.', Logger::ERROR);
if ($value instanceof DbArray) {
@@ -81,7 +84,6 @@ class MysqlArray implements DbArray
}
});
-
return $instance;
}
@@ -94,33 +96,16 @@ class MysqlArray implements DbArray
* The offset being checked.
*
*
- * @return bool true if the offset exists, otherwise false
+ * @return Promise true if the offset exists, otherwise false
* @throws \Throwable
*/
- public function offsetExists($index)
+ public function offsetExists($index): Promise
{
- return $this->offsetGet($index) !== null;
- }
-
- /**
- * Get value for an offset
- *
- * @link https://php.net/manual/en/arrayiterator.offsetget.php
- *
- * @param string $index
- * The offset to get the value from.
- *
- *
- * @return mixed The value at offset index.
- * @throws \Throwable
- */
- public function offsetGet($index)
- {
- return wait($this->offsetGetAsync($index));
+ return call(fn() => yield $this->offsetGet($index) !== null);
}
- public function offsetGetAsync(string $offset): Promise
+ public function offsetGet($offset): Promise
{
return call(function() use($offset) {
if ($cached = $this->getCache($offset)) {
@@ -150,19 +135,15 @@ class MysqlArray implements DbArray
*
* @param $value
*
- * @return void
+ * @return Promise
* @throws \Throwable
*/
- public function offsetSet($index, $value)
- {
- wait($this->offsetSetAsync($index, $value));
- }
- public function offsetSetAsync($index, $value): Promise
+ public function offsetSet($index, $value): void
{
$this->setCache($index, $value);
- return $this->request("
+ $this->request("
INSERT INTO `{$this->table}`
SET `key` = :index, `value` = :value
ON DUPLICATE KEY UPDATE `value` = :value
@@ -183,14 +164,14 @@ class MysqlArray implements DbArray
* The offset to unset.
*
*
- * @return void
+ * @return Promise
* @throws \Throwable
*/
- public function offsetUnset($index)
+ public function offsetUnset($index): Promise
{
$this->unsetCache($index);
- $this->syncRequest("
+ return $this->request("
DELETE FROM `{$this->table}`
WHERE `key` = :index
",
@@ -211,7 +192,7 @@ class MysqlArray implements DbArray
$rows = $this->syncRequest("SELECT `key`, `value` FROM {$this->table}");
$result = [];
foreach ($rows as $row) {
- $result[$row['key']] = unserialize($row['value']);
+ $result[$row['key']] = $this->getValue($row);
}
return $result;
@@ -225,7 +206,7 @@ class MysqlArray implements DbArray
while (yield $request->advance()) {
$row = $request->getCurrent();
- yield $emit($this->getValue($row));
+ yield $emit([$row['key'], $this->getValue($row)]);
}
});
}
@@ -234,40 +215,16 @@ class MysqlArray implements DbArray
* Count elements
*
* @link https://php.net/manual/en/arrayiterator.count.php
- * @return int The number of elements or public properties in the associated
+ * @return Promise The number of elements or public properties in the associated
* array or object, respectively.
* @throws \Throwable
*/
- public function count(): int
+ public function count(): Promise
{
- $row = $this->syncRequest("SELECT count(`key`) as `count` FROM {$this->table}");
- return $row[0]['count'] ?? 0;
- }
-
- /**
- * Rewind array back to the start
- *
- * @link https://php.net/manual/en/arrayiterator.rewind.php
- * @return void
- * @throws \Throwable
- */
- public function rewind()
- {
- $this->key = null;
- $this->key();
- $this->current = null;
- }
-
- /**
- * Return current array entry
- *
- * @link https://php.net/manual/en/arrayiterator.current.php
- * @return mixed The current array entry.
- * @throws \Throwable
- */
- public function current()
- {
- return $this->current ?: $this->offsetGet($this->key());
+ return call(function(){
+ $row = yield $this->request("SELECT count(`key`) as `count` FROM {$this->table}");
+ return $row[0]['count'] ?? 0;
+ });
}
private function getValue(array $row)
@@ -281,71 +238,6 @@ class MysqlArray implements DbArray
return null;
}
- /**
- * Return current array key
- *
- * @link https://php.net/manual/en/arrayiterator.key.php
- * @return string|float|int|bool|null The current array key.
- * @throws \Throwable
- */
- public function key(): ?string
- {
- if ($this->key === null) {
- $row = $this->syncRequest(
- "SELECT `key` FROM {$this->table} ORDER BY `key` LIMIT 1"
- );
- $this->key = $row[0]['key'] ?? null;
- }
- return $this->key;
- }
-
- /**
- * Move to next entry
- *
- * @link https://php.net/manual/en/arrayiterator.next.php
- * @return void
- * @throws \Throwable
- */
- public function next()
- {
- $row = $this->syncRequest(
- "SELECT `key`, `value` FROM {$this->table} WHERE `key` > :key ORDER BY `key` LIMIT 1",
- ['key' => $this->key()]
- );
-
- $this->key = $row[0]['key'] ?? null;
- $this->current = $this->getValue($row);
- }
-
- /**
- * Check whether array contains more entries
- *
- * @link https://php.net/manual/en/arrayiterator.valid.php
- * @return bool
- * @throws \Throwable
- */
- public function valid():bool
- {
- return $this->key !== null;
- }
-
- /**
- * Seek to position
- * @link https://php.net/manual/en/arrayiterator.seek.php
- * @param int $position
- * The position to seek to.
- *
- * @return void
- */
- public function seek($position)
- {
- $row = $this->syncRequest(
- "SELECT `key` FROM {$this->table} ORDER BY `key` LIMIT 1 OFFSET :position",
- ['offset' => $position]
- );
- $this->key = $row[0]['key'] ?? $this->key;
- }
-
public static function getDbConnection(array $settings): Pool
{
return Mysql::getConnection(
@@ -384,6 +276,14 @@ class MysqlArray implements DbArray
");
} catch (\Throwable $e) {
Logger::log("Cant rename table {$from} to {$to}", Logger::WARNING);
+
+ try {
+ $this->syncRequest("
+ DROP TABLE {$from};
+ ");
+ } catch (\Throwable $e) {
+ Logger::log("Cant drop table {$from}", Logger::WARNING);
+ }
}
}
diff --git a/src/danog/MadelineProto/InternalDoc.php b/src/danog/MadelineProto/InternalDoc.php
index b14eac10..d49a4120 100644
--- a/src/danog/MadelineProto/InternalDoc.php
+++ b/src/danog/MadelineProto/InternalDoc.php
@@ -4285,11 +4285,12 @@ class InternalDoc extends APIFactory
*
* @param array $user User info
*
- * @return void
+ * @return \Generator
+ * @throws Exception
*/
- public function addUser(array $user): void
+ public function addUser(array $user): \Generator
{
- $this->API->addUser($user);
+ yield from $this->API->addUser($user);
}
/**
* Call promise $b after promise $a.
@@ -4754,11 +4755,11 @@ class InternalDoc extends APIFactory
*
* @param mixed $id Chat ID
*
- * @return integer
+ * @return \Generator
*/
- public function fullChatLastUpdated($id): int
+ public function fullChatLastUpdated($id): \Generator
{
- return $this->API->fullChatLastUpdated($id);
+ return yield from $this->API->fullChatLastUpdated($id);
}
/**
* Get info about the logged-in user, not cached.
diff --git a/src/danog/MadelineProto/MTProto.php b/src/danog/MadelineProto/MTProto.php
index 313bfc18..ab70a34d 100644
--- a/src/danog/MadelineProto/MTProto.php
+++ b/src/danog/MadelineProto/MTProto.php
@@ -22,6 +22,7 @@ namespace danog\MadelineProto;
use Amp\Dns\Resolver;
use Amp\File\StatCache;
use Amp\Http\Client\HttpClient;
+use Amp\Promise;
use danog\MadelineProto\Async\AsyncConstruct;
use danog\MadelineProto\Db\DbArray;
use danog\MadelineProto\Db\DbPropertiesFabric;
@@ -288,13 +289,13 @@ class MTProto extends AsyncConstruct implements TLCallback
/**
* Cache of usernames for chats
*
- * @var DbArray
+ * @var DbArray|Promise[]
*/
public $usernames;
/**
* Cached parameters for fetching channel participants.
*
- * @var DbArray
+ * @var DbArray|Promise[]
*/
public $channel_participants;
/**
@@ -312,7 +313,7 @@ class MTProto extends AsyncConstruct implements TLCallback
/**
* Full chat info database.
*
- * @var DbArray
+ * @var DbArray|Promise[]
*/
public $full_chats;
/**
@@ -454,7 +455,7 @@ class MTProto extends AsyncConstruct implements TLCallback
// Parse and store settings
yield from $this->updateSettings($settings, false);
$this->logger->logger(Lang::$current_lang['inst_dc'], Logger::ULTRA_VERBOSE);
- $this->cleanupProperties();
+ yield from $this->cleanupProperties();
// Load rsa keys
$this->logger->logger(Lang::$current_lang['load_rsa'], Logger::ULTRA_VERBOSE);
$this->rsa_keys = [];
@@ -565,7 +566,7 @@ class MTProto extends AsyncConstruct implements TLCallback
];
}
- public function initDb(bool $reset = false): void
+ public function initDb(bool $reset = false): \Generator
{
foreach ($this->dbProperies as $property => $type) {
if ($reset) {
@@ -575,18 +576,16 @@ class MTProto extends AsyncConstruct implements TLCallback
}
}
- if (!$reset && count($this->usernames) === 0) {
- \Amp\Loop::run(function() {
- $this->logger('Filling database cache. This can take few minutes.', Logger::WARNING);
- $iterator = $this->chats->getIterator();
- while (yield $iterator->advance()) {
- $chat = $iterator->getCurrent();
- if (isset($chat['username'])) {
- $this->usernames->offsetSetAsync(\strtolower($chat['username']), $this->getId($chat));
- }
+ if (!$reset && yield $this->usernames->count() === 0) {
+ $this->logger('Filling database cache. This can take few minutes.', Logger::WARNING);
+ $iterator = $this->chats->getIterator();
+ while (yield $iterator->advance()) {
+ [$id, $chat] = $iterator->getCurrent();
+ if (isset($chat['username'])) {
+ $this->usernames[\strtolower($chat['username'])] = $this->getId($chat);
}
- $this->logger('Cache filled.', Logger::WARNING);
- });
+ }
+ $this->logger('Cache filled.', Logger::WARNING);
}
}
@@ -802,13 +801,17 @@ class MTProto extends AsyncConstruct implements TLCallback
$this->TL->init($this->settings['tl_schema']['src'], $callbacks);
}
- $this->initDb();
+ yield from $this->initDb();
}
+
/**
* Upgrade MadelineProto instance.
*
* @return \Generator
+ * @throws Exception
+ * @throws RPCErrorException
+ * @throws \Throwable
*/
private function upgradeMadelineProto(): \Generator
{
@@ -831,16 +834,19 @@ class MTProto extends AsyncConstruct implements TLCallback
unset($settings['authorization']['rsa_key']);
}
- $this->initDb();
+ yield from $this->initDb();
if (!isset($this->secret_chats)) {
$this->secret_chats = [];
}
- foreach ($this->full_chats as $id => $full) {
+ $iterator = $this->full_chats->getIterator();
+ while (yield $iterator->advance()) {
+ [$id, $full] = $iterator->getCurrent();
if (isset($full['full'], $full['last_update'])) {
$this->full_chats[$id] = ['full' => $full['full'], 'last_update' => $full['last_update']];
}
}
+
foreach ($this->secret_chats as $key => &$chat) {
if (!\is_array($chat)) {
unset($this->secret_chats[$key]);
@@ -942,7 +948,7 @@ class MTProto extends AsyncConstruct implements TLCallback
$force = true;
}
// Cleanup old properties, init new stuffs
- $this->cleanupProperties();
+ yield from $this->cleanupProperties();
// Update TL callbacks
$callbacks = [$this, $this->referenceDatabase];
if (!($this->authorization['user']['bot'] ?? false)) {
@@ -1509,9 +1515,9 @@ class MTProto extends AsyncConstruct implements TLCallback
*
* @internal
*
- * @return void
+ * @return \Generator
*/
- public function resetSession(): void
+ public function resetSession(): \Generator
{
if (isset($this->seqUpdater)) {
$this->seqUpdater->signal(true);
@@ -1544,7 +1550,7 @@ class MTProto extends AsyncConstruct implements TLCallback
$this->updates = [];
$this->secret_chats = [];
- $this->initDb(true);
+ yield from $this->initDb(true);
$this->tos = ['expires' => 0, 'accepted' => true];
$this->referenceDatabase = new ReferenceDatabase($this);
diff --git a/src/danog/MadelineProto/MTProtoTools/PeerHandler.php b/src/danog/MadelineProto/MTProtoTools/PeerHandler.php
index 31f7f0e6..60bcb749 100644
--- a/src/danog/MadelineProto/MTProtoTools/PeerHandler.php
+++ b/src/danog/MadelineProto/MTProtoTools/PeerHandler.php
@@ -22,6 +22,7 @@ namespace danog\MadelineProto\MTProtoTools;
use Amp\Http\Client\Request;
use danog\Decoder\FileId;
use danog\Decoder\PhotoSizeSource\PhotoSizeSourceDialogPhoto;
+use danog\MadelineProto\Db\DbArray;
use const danog\Decoder\PROFILE_PHOTO;
@@ -82,26 +83,29 @@ trait PeerHandler
{
$this->supportUser = $support['user']['id'];
}
+
/**
* Add user info.
*
* @param array $user User info
*
- * @return void
+ * @return \Generator
+ * @throws \danog\MadelineProto\Exception
*/
- public function addUser(array $user): void
+ public function addUser(array $user): \Generator
{
+ $existingChat = yield $this->chats[$user['id']];
if (!isset($user['access_hash']) && !($user['min'] ?? false)) {
- if (isset($this->chats[$user['id']]['access_hash']) && $this->chats[$user['id']]['access_hash']) {
+ if (!empty($existingChat['access_hash'])) {
$this->logger->logger("No access hash with user {$user['id']}, using backup");
- $user['access_hash'] = $this->chats[$user['id']]['access_hash'];
+ $user['access_hash'] = $existingChat['access_hash'];
} elseif (!isset($this->caching_simple[$user['id']]) && !(isset($user['username']) && isset($this->caching_simple_username[$user['username']]))) {
$this->logger->logger("No access hash with user {$user['id']}, trying to fetch by ID...");
if (isset($user['username']) && !isset($this->caching_simple_username[$user['username']])) {
$this->caching_possible_username[$user['id']] = $user['username'];
}
$this->cachePwrChat($user['id'], false, true);
- } elseif (isset($user['username']) && !isset($this->chats[$user['id']]) && !isset($this->caching_simple_username[$user['username']])) {
+ } elseif (isset($user['username']) && !$existingChat && !isset($this->caching_simple_username[$user['username']])) {
$this->logger->logger("No access hash with user {$user['id']}, trying to fetch by username...");
$this->cachePwrChat($user['username'], false, true);
} else {
@@ -111,13 +115,13 @@ trait PeerHandler
}
switch ($user['_']) {
case 'user':
- if (!isset($this->chats[$user['id']]) || $this->chats[$user['id']] !== $user) {
+ if (!$existingChat || $existingChat !== $user) {
$this->logger->logger("Updated user {$user['id']}", \danog\MadelineProto\Logger::ULTRA_VERBOSE);
- if (($user['min'] ?? false) && isset($this->chats[$user['id']]) && !($this->chats[$user['id']]['min'] ?? false)) {
+ if (($user['min'] ?? false) && !($existingChat['min'] ?? false)) {
$this->logger->logger("{$user['id']} is min, filling missing fields", \danog\MadelineProto\Logger::ULTRA_VERBOSE);
- if (isset($this->chats[$user['id']]['access_hash'])) {
+ if (isset($existingChat['access_hash'])) {
$user['min'] = false;
- $user['access_hash'] = $this->chats[$user['id']]['access_hash'];
+ $user['access_hash'] = $existingChat['access_hash'];
}
}
$this->chats[$user['id']] = $user;
@@ -138,7 +142,7 @@ trait PeerHandler
*
* @internal
*
- * @return void
+ * @return \Generator
*/
public function addChat($chat): \Generator
{
@@ -146,7 +150,8 @@ trait PeerHandler
case 'chat':
case 'chatEmpty':
case 'chatForbidden':
- if (!isset($this->chats[-$chat['id']]) || $this->chats[-$chat['id']] !== $chat) {
+ $existingChat = yield $this->chats[-$chat['id']];
+ if (!$existingChat || $existingChat !== $chat) {
$this->logger->logger("Updated chat -{$chat['id']}", \danog\MadelineProto\Logger::ULTRA_VERBOSE);
$this->chats[-$chat['id']] = $chat;
$this->cacheChatUsername(-$chat['id'], $chat);
@@ -165,7 +170,7 @@ trait PeerHandler
$this->caching_possible_username[$bot_api_id] = $chat['username'];
}
$this->cachePwrChat($bot_api_id, false, true);
- } elseif (isset($chat['username']) && !isset($this->chats[$bot_api_id]) && !isset($this->caching_simple_username[$chat['username']])) {
+ } elseif (isset($chat['username']) && !(yield $this->chats[$bot_api_id]) && !isset($this->caching_simple_username[$chat['username']])) {
$this->logger->logger("No access hash with {$chat['_']} {$bot_api_id}, trying to fetch by username...");
$this->cachePwrChat($chat['username'], false, true);
} else {
@@ -173,11 +178,12 @@ trait PeerHandler
}
return;
}
- if (!isset($this->chats[$bot_api_id]) || $this->chats[$bot_api_id] !== $chat) {
+ $existingChat = yield $this->chats[$bot_api_id];
+ if (!$existingChat || $existingChat !== $chat) {
$this->logger->logger("Updated chat {$bot_api_id}", \danog\MadelineProto\Logger::ULTRA_VERBOSE);
- if (($chat['min'] ?? false) && isset($this->chats[$bot_api_id]) && !($this->chats[$bot_api_id]['min'] ?? false)) {
+ if (($chat['min'] ?? false) && $existingChat && !($existingChat['min'] ?? false)) {
$this->logger->logger("{$bot_api_id} is min, filling missing fields", \danog\MadelineProto\Logger::ULTRA_VERBOSE);
- $newchat = $this->chats[$bot_api_id];
+ $newchat = $existingChat;
foreach (['title', 'username', 'photo', 'banned_rights', 'megagroup', 'verified'] as $field) {
if (isset($chat[$field])) {
$newchat[$field] = $chat[$field];
@@ -187,7 +193,8 @@ trait PeerHandler
}
$this->chats[$bot_api_id] = $chat;
$this->cacheChatUsername($bot_api_id, $chat);
- if ($this->settings['peer']['full_fetch'] && (!isset($this->full_chats[$bot_api_id]) || $this->full_chats[$bot_api_id]['full']['participants_count'] !== (yield from $this->getFullInfo($bot_api_id))['full']['participants_count'])) {
+ $fullChat = yield $this->full_chats[$bot_api_id];
+ if ($this->settings['peer']['full_fetch'] && (!$fullChat || $fullChat['full']['participants_count'] !== (yield from $this->getFullInfo($bot_api_id))['full']['participants_count'])) {
$this->cachePwrChat($bot_api_id, $this->settings['peer']['full_fetch'], true);
}
}
@@ -224,7 +231,9 @@ trait PeerHandler
public function peerIsset($id): \Generator
{
try {
- return isset($this->chats[(yield from $this->getInfo($id))['bot_api_id']]);
+ $info = yield from $this->getInfo($id);
+ $chatId = $info['bot_api_id'];
+ return (yield $this->chats[$chatId]) !== null;
} catch (\danog\MadelineProto\Exception $e) {
return false;
} catch (\danog\MadelineProto\RPCErrorException $e) {
@@ -481,7 +490,7 @@ trait PeerHandler
}
$tried_simple = false;
if (\is_numeric($id)) {
- if (!isset($this->chats[$id])) {
+ if (! yield $this->chats[$id]) {
try {
$this->logger->logger("Try fetching {$id} with access hash 0");
$this->caching_simple[$id] = true;
@@ -505,15 +514,15 @@ trait PeerHandler
$tried_simple = true;
}
}
- if (isset($this->chats[$id])) {
- if (($this->chats[$id]['min'] ?? false) && $this->minDatabase->hasPeer($id) && !isset($this->caching_full_info[$id])) {
+ if (yield $this->chats[$id]) {
+ if (((yield $this->chats[$id])['min'] ?? false) && $this->minDatabase->hasPeer($id) && !isset($this->caching_full_info[$id])) {
$this->caching_full_info[$id] = true;
$this->logger->logger("Only have min peer for {$id} in database, trying to fetch full info");
try {
if ($id < 0) {
- yield from $this->methodCallAsyncRead('channels.getChannels', ['id' => [$this->genAll($this->chats[$id], $folder_id)['InputChannel']]], ['datacenter' => $this->datacenter->curdc]);
+ yield from $this->methodCallAsyncRead('channels.getChannels', ['id' => [$this->genAll(yield $this->chats[$id], $folder_id)['InputChannel']]], ['datacenter' => $this->datacenter->curdc]);
} else {
- yield from $this->methodCallAsyncRead('users.getUsers', ['id' => [$this->genAll($this->chats[$id], $folder_id)['InputUser']]], ['datacenter' => $this->datacenter->curdc]);
+ yield from $this->methodCallAsyncRead('users.getUsers', ['id' => [$this->genAll(yield $this->chats[$id], $folder_id)['InputUser']]], ['datacenter' => $this->datacenter->curdc]);
}
} catch (\danog\MadelineProto\Exception $e) {
$this->logger->logger($e->getMessage(), \danog\MadelineProto\Logger::WARNING);
@@ -524,10 +533,10 @@ trait PeerHandler
}
}
try {
- return $this->genAll($this->chats[$id], $folder_id);
+ return $this->genAll(yield $this->chats[$id], $folder_id);
} catch (\danog\MadelineProto\Exception $e) {
if ($e->getMessage() === 'This peer is not present in the internal peer database') {
- unset($this->chats[$id]);
+ yield $this->chats->offsetUnset($id);/** @uses DbArray::offsetUnset() */
} else {
throw $e;
}
@@ -574,10 +583,10 @@ trait PeerHandler
}
return yield from $this->getInfo($this->supportUser);
}
- if ($bot_api_id = $this->usernames[$id] ?? null) {
- $chat = $this->chats[$bot_api_id];
+ if ($bot_api_id = yield $this->usernames[$id]) {
+ $chat = yield $this->chats[$bot_api_id];
if (empty($chat['username']) || \strtolower($chat['username']) !== $id) {
- unset($this->usernames[$id]);
+ yield $this->usernames->offsetUnset($id); /** @uses DbArray::offsetUnset() */
}
if (isset($chat['username']) && \strtolower($chat['username']) === $id) {
@@ -586,9 +595,9 @@ trait PeerHandler
$this->logger->logger("Only have min peer for {$bot_api_id} in database, trying to fetch full info");
try {
if ($bot_api_id < 0) {
- yield from $this->methodCallAsyncRead('channels.getChannels', ['id' => [$this->genAll($this->chats[$bot_api_id], $folder_id)['InputChannel']]], ['datacenter' => $this->datacenter->curdc]);
+ yield from $this->methodCallAsyncRead('channels.getChannels', ['id' => [$this->genAll(yield $this->chats[$bot_api_id], $folder_id)['InputChannel']]], ['datacenter' => $this->datacenter->curdc]);
} else {
- yield from $this->methodCallAsyncRead('users.getUsers', ['id' => [$this->genAll($this->chats[$bot_api_id], $folder_id)['InputUser']]], ['datacenter' => $this->datacenter->curdc]);
+ yield from $this->methodCallAsyncRead('users.getUsers', ['id' => [$this->genAll(yield $this->chats[$bot_api_id], $folder_id)['InputUser']]], ['datacenter' => $this->datacenter->curdc]);
}
} catch (\danog\MadelineProto\Exception $e) {
$this->logger->logger($e->getMessage(), \danog\MadelineProto\Logger::WARNING);
@@ -598,7 +607,7 @@ trait PeerHandler
unset($this->caching_full_info[$bot_api_id]);
}
}
- return $this->genAll($this->chats[$bot_api_id], $folder_id);
+ return $this->genAll(yield $this->chats[$bot_api_id], $folder_id);
}
}
@@ -674,11 +683,11 @@ trait PeerHandler
*
* @param mixed $id Chat ID
*
- * @return integer
+ * @return \Generator
*/
- public function fullChatLastUpdated($id): int
+ public function fullChatLastUpdated($id): \Generator
{
- return isset($this->full_chats[$id]['last_update']) ? $this->full_chats[$id]['last_update'] : 0;
+ return (yield $this->full_chats[$id])['last_update'] ?? 0;
}
/**
* Get full info about peer, returns an FullInfo object.
@@ -692,8 +701,8 @@ trait PeerHandler
public function getFullInfo($id): \Generator
{
$partial = (yield from $this->getInfo($id));
- if (\time() - $this->fullChatLastUpdated($partial['bot_api_id']) < (isset($this->settings['peer']['full_info_cache_time']) ? $this->settings['peer']['full_info_cache_time'] : 0)) {
- return \array_merge($partial, $this->full_chats[$partial['bot_api_id']]);
+ if (\time() - (yield from $this->fullChatLastUpdated($partial['bot_api_id'])) < (isset($this->settings['peer']['full_info_cache_time']) ? $this->settings['peer']['full_info_cache_time'] : 0)) {
+ return \array_merge($partial, yield $this->full_chats[$partial['bot_api_id']]);
}
switch ($partial['type']) {
case 'user':
@@ -912,10 +921,10 @@ trait PeerHandler
}
throw $e;
}
- if ($cached = $gres['_'] === 'channels.channelParticipantsNotModified') {
- $gres = $this->fetchParticipantsCache($channel, $filter, $q, $offset, $limit);
+ if ($cached = ($gres['_'] === 'channels.channelParticipantsNotModified')) {
+ $gres = yield from $this->fetchParticipantsCache($channel, $filter, $q, $offset, $limit);
} else {
- $this->storeParticipantsCache($gres, $channel, $filter, $q, $offset, $limit);
+ yield from $this->storeParticipantsCache($gres, $channel, $filter, $q, $offset, $limit);
}
if ($last_count !== -1 && $last_count !== $gres['count']) {
$has_more = true;
@@ -975,11 +984,10 @@ trait PeerHandler
}
private function fetchParticipantsCache($channel, $filter, $q, $offset, $limit)
{
- return $this->channel_participants[$channel['channel_id']][$filter][$q][$offset][$limit];
+ return (yield $this->channel_participants[$channel['channel_id']])[$filter][$q][$offset][$limit];
}
- private function storeParticipantsCache($gres, $channel, $filter, $q, $offset, $limit)
+ private function storeParticipantsCache($gres, $channel, $filter, $q, $offset, $limit): \Generator
{
- //return;
unset($gres['users']);
$ids = [];
foreach ($gres['participants'] as $participant) {
@@ -987,13 +995,13 @@ trait PeerHandler
}
\sort($ids, SORT_NUMERIC);
$gres['hash'] = \danog\MadelineProto\Tools::genVectorHash($ids);
- $participant = $this->channel_participants[$channel['channel_id']];
+ $participant = yield $this->channel_participants[$channel['channel_id']];
$participant[$filter][$q][$offset][$limit] = $gres;
$this->channel_participants[$channel['channel_id']] = $participant;
}
private function getParticipantsHash($channel, $filter, $q, $offset, $limit)
{
- return isset($this->channel_participants[$channel['channel_id']][$filter][$q][$offset][$limit]) ? $this->channel_participants[$channel['channel_id']][$filter][$q][$offset][$limit]['hash'] : 0;
+ return (yield $this->channel_participants[$channel['channel_id']])[$filter][$q][$offset][$limit]['hash'] ?? 0;
}
private function storeDb($res, $force = false): \Generator
{
@@ -1054,6 +1062,12 @@ trait PeerHandler
}
}
if ($res['_'] === 'contacts.resolvedPeer') {
+ foreach ($res['chats'] as $chat) {
+ yield from $this->addChat($chat);
+ }
+ foreach ($res['users'] as $user) {
+ yield from $this->addUser($user);
+ }
return $res;
}
return false;
diff --git a/src/danog/MadelineProto/MTProtoTools/ReferenceDatabase.php b/src/danog/MadelineProto/MTProtoTools/ReferenceDatabase.php
index 72dd357b..b877a57c 100644
--- a/src/danog/MadelineProto/MTProtoTools/ReferenceDatabase.php
+++ b/src/danog/MadelineProto/MTProtoTools/ReferenceDatabase.php
@@ -422,10 +422,10 @@ class ReferenceDatabase implements TLCallback
break;
// Peer + photo ID
case self::PEER_PHOTO_ORIGIN:
- if (isset($this->API->full_chats[$origin['peer']]['last_update'])) {
- $chat = $this->API->full_chats[$origin['peer']];
- $chat['last_update'] = 0;
- $this->API->full_chats[$origin['peer']] = $chat;
+ $fullChat = yield $this->API->full_chats[$origin['peer']];
+ if (isset($fullChat['last_update'])) {
+ $fullChat['last_update'] = 0;
+ $this->API->full_chats[$origin['peer']] = $fullChat;
}
$this->API->getFullInfo($origin['peer']);
break;
diff --git a/src/danog/MadelineProto/MTProtoTools/UpdateHandler.php b/src/danog/MadelineProto/MTProtoTools/UpdateHandler.php
index facb7475..942e4d7c 100644
--- a/src/danog/MadelineProto/MTProtoTools/UpdateHandler.php
+++ b/src/danog/MadelineProto/MTProtoTools/UpdateHandler.php
@@ -332,7 +332,7 @@ trait UpdateHandler
}
if (\in_array($update['_'], ['updateUserName', 'updateUserPhone', 'updateUserBlocked', 'updateUserPhoto', 'updateContactRegistered', 'updateContactLink'])) {
$id = $this->getId($update);
- $chat = $this->full_chats[$id];
+ $chat = yield $this->full_chats[$id];
$chat['last_update'] = 0;
$this->full_chats[$id] = $chat;
yield from $this->getFullInfo($id);
diff --git a/src/danog/MadelineProto/Wrappers/DialogHandler.php b/src/danog/MadelineProto/Wrappers/DialogHandler.php
index d7de6417..26f0eab1 100644
--- a/src/danog/MadelineProto/Wrappers/DialogHandler.php
+++ b/src/danog/MadelineProto/Wrappers/DialogHandler.php
@@ -32,13 +32,15 @@ trait DialogHandler
{
if ($this->authorization['user']['bot']) {
$res = [];
- foreach ($this->chats as $chat) {
+ /** @uses DbArray::getIterator() */
+ $iterator = $this->chats->getIterator();
+ while (yield $iterator->advance()) {
+ [$id, $chat] = $iterator->getCurrent();
try {
$res[] = $this->genAll($chat)['Peer'];
} catch (\Throwable $e) {
continue;
}
-
}
return $res;
}
diff --git a/src/danog/MadelineProto/Wrappers/Login.php b/src/danog/MadelineProto/Wrappers/Login.php
index 64809d66..82536ea9 100644
--- a/src/danog/MadelineProto/Wrappers/Login.php
+++ b/src/danog/MadelineProto/Wrappers/Login.php
@@ -35,7 +35,7 @@ trait Login
public function logout(): \Generator
{
yield from $this->methodCallAsyncRead('auth.logOut', [], ['datacenter' => $this->datacenter->curdc]);
- $this->resetSession();
+ yield from $this->resetSession();
$this->logger->logger(\danog\MadelineProto\Lang::$current_lang['logout_ok'], \danog\MadelineProto\Logger::NOTICE);
$this->startUpdateSystem();
return true;
From 0c59e6ef4249cf9717405bab3e08a6a88b30fa4d Mon Sep 17 00:00:00 2001
From: Alexander Pankratov
Date: Sun, 3 May 2020 16:16:20 +0300
Subject: [PATCH 12/29] Load submodules
---
composer.json | 3 +++
1 file changed, 3 insertions(+)
diff --git a/composer.json b/composer.json
index 0555de72..71ba0793 100644
--- a/composer.json
+++ b/composer.json
@@ -80,6 +80,9 @@
"url": "https://github.com/danog/dns"
}],
"scripts": {
+ "post-autoload-dump": [
+ "git submodule init && git submodule update"
+ ],
"build": [
"@docs",
"@cs-fix",
From ae9edd5512cd82a06ff10eea2d49e5acdb350894 Mon Sep 17 00:00:00 2001
From: Alexander Pankratov
Date: Sun, 3 May 2020 17:04:30 +0300
Subject: [PATCH 13/29] Mysql errors catch
---
src/danog/MadelineProto/Db/MysqlArray.php | 33 +++++++++++------------
1 file changed, 15 insertions(+), 18 deletions(-)
diff --git a/src/danog/MadelineProto/Db/MysqlArray.php b/src/danog/MadelineProto/Db/MysqlArray.php
index cbb3583e..33f62137 100644
--- a/src/danog/MadelineProto/Db/MysqlArray.php
+++ b/src/danog/MadelineProto/Db/MysqlArray.php
@@ -7,6 +7,7 @@ use Amp\Mysql\Pool;
use Amp\Producer;
use Amp\Promise;
use Amp\Sql\ResultSet;
+use danog\MadelineProto\Exception;
use danog\MadelineProto\Logger;
use function Amp\call;
use function Amp\Promise\wait;
@@ -49,7 +50,7 @@ class MysqlArray implements DbArray
$instance->db = static::getDbConnection($settings);
$instance->ttl = $settings['cache_ttl'] ?? $instance->ttl;
- if ($value instanceof static) {
+ if ($value instanceof static && $value->table) {
if (
mb_strpos($value->table, 'tmp') === 0 &&
mb_strpos($instance->table, 'tmp') !== 0
@@ -270,22 +271,12 @@ class MysqlArray implements DbArray
private function renameTable(string $from, string $to)
{
- try {
- $this->syncRequest("
- ALTER TABLE {$from} RENAME TO {$to};
- ");
- } catch (\Throwable $e) {
- Logger::log("Cant rename table {$from} to {$to}", Logger::WARNING);
-
- try {
- $this->syncRequest("
- DROP TABLE {$from};
- ");
- } catch (\Throwable $e) {
- Logger::log("Cant drop table {$from}", Logger::WARNING);
- }
- }
-
+ $this->syncRequest("
+ ALTER TABLE {$from} RENAME TO {$to};
+ ");
+ $this->syncRequest("
+ DROP TABLE IF EXISTS {$from};
+ ");
}
/**
@@ -318,7 +309,13 @@ class MysqlArray implements DbArray
return [];
}
- $request = yield $this->db->execute($query, $params);
+ try {
+ $request = yield $this->db->execute($query, $params);
+ } catch (\Throwable $e) {
+ Logger::log($e, Logger::ERROR);
+ return [];
+ }
+
$result = [];
if ($request instanceof ResultSet) {
while (yield $request->advance()) {
From ab168ce6553758e40a587f0dd2b66fe37e9222fa Mon Sep 17 00:00:00 2001
From: Alexander Pankratov
Date: Mon, 4 May 2020 23:09:39 +0300
Subject: [PATCH 14/29] Additional mysql settings
---
src/danog/MadelineProto/Db/Mysql.php | 10 ++++++++--
src/danog/MadelineProto/Db/MysqlArray.php | 2 ++
src/danog/MadelineProto/MTProto.php | 6 ++++--
3 files changed, 14 insertions(+), 4 deletions(-)
diff --git a/src/danog/MadelineProto/Db/Mysql.php b/src/danog/MadelineProto/Db/Mysql.php
index 07c9051b..736ada7f 100644
--- a/src/danog/MadelineProto/Db/Mysql.php
+++ b/src/danog/MadelineProto/Db/Mysql.php
@@ -4,6 +4,7 @@ namespace danog\MadelineProto\Db;
use Amp\Mysql\ConnectionConfig;
use Amp\Mysql\Pool;
+use Amp\Sql\Common\ConnectionPool;
use function Amp\call;
use function Amp\Mysql\Pool;
use function Amp\Promise\wait;
@@ -20,6 +21,9 @@ class Mysql
* @param string $password
* @param string $db
*
+ * @param int $maxConnections
+ * @param int $idleTimeout
+ *
* @return Pool
* @throws \Amp\Sql\ConnectionException
* @throws \Amp\Sql\FailureException
@@ -30,7 +34,9 @@ class Mysql
int $port = 3306,
string $user = 'root',
string $password = '',
- string $db = 'MadelineProto'
+ string $db = 'MadelineProto',
+ int $maxConnections = ConnectionPool::DEFAULT_MAX_CONNECTIONS,
+ int $idleTimeout = ConnectionPool::DEFAULT_IDLE_TIMEOUT
): Pool
{
$dbKey = "$host:$port:$db";
@@ -40,7 +46,7 @@ class Mysql
);
static::createDb($config);
- static::$connections[$dbKey] = pool($config);
+ static::$connections[$dbKey] = pool($config, $maxConnections, $idleTimeout);
}
return static::$connections[$dbKey];
diff --git a/src/danog/MadelineProto/Db/MysqlArray.php b/src/danog/MadelineProto/Db/MysqlArray.php
index 33f62137..9bb5aa96 100644
--- a/src/danog/MadelineProto/Db/MysqlArray.php
+++ b/src/danog/MadelineProto/Db/MysqlArray.php
@@ -247,6 +247,8 @@ class MysqlArray implements DbArray
$settings['user'],
$settings['password'],
$settings['database'],
+ $settings['max_connections'],
+ $settings['idle_timeout']
);
}
diff --git a/src/danog/MadelineProto/MTProto.php b/src/danog/MadelineProto/MTProto.php
index ab70a34d..d9e15291 100644
--- a/src/danog/MadelineProto/MTProto.php
+++ b/src/danog/MadelineProto/MTProto.php
@@ -1320,8 +1320,10 @@ class MTProto extends AsyncConstruct implements TLCallback
'port' => 3306,
'user' => 'root',
'password' => '',
- 'database' => 'MadelineProto',
- 'cache_ttl' => '+1 day',
+ 'database' => 'MadelineProto', //will be created automatically
+ 'max_connections' => 10,
+ 'idle_timeout' => 60,
+ 'cache_ttl' => '+1 day', //keep records in memory after last read
]
],
'upload' => ['allow_automatic_upload' => true, 'part_size' => 512 * 1024, 'parallel_chunks' => 20], 'download' => ['report_broken_media' => true, 'part_size' => 1024 * 1024, 'parallel_chunks' => 20], 'pwr' => [
From dedf20ea1bbc0e44063e51322fce8041318c7205 Mon Sep 17 00:00:00 2001
From: Alexander Pankratov
Date: Tue, 5 May 2020 20:05:32 +0300
Subject: [PATCH 15/29] Update create table statements.
---
src/danog/MadelineProto/Db/Mysql.php | 5 +++--
src/danog/MadelineProto/Db/MysqlArray.php | 3 +++
2 files changed, 6 insertions(+), 2 deletions(-)
diff --git a/src/danog/MadelineProto/Db/Mysql.php b/src/danog/MadelineProto/Db/Mysql.php
index 736ada7f..593a94ab 100644
--- a/src/danog/MadelineProto/Db/Mysql.php
+++ b/src/danog/MadelineProto/Db/Mysql.php
@@ -61,12 +61,13 @@ class Mysql
*/
private static function createDb(ConnectionConfig $config)
{
- wait(call(function() use($config) {
+ wait(call(static function() use($config) {
$db = $config->getDatabase();
$connection = pool($config->withDatabase(null));
yield $connection->query("
CREATE DATABASE IF NOT EXISTS `{$db}`
- CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci
+ CHARACTER SET 'utf8mb4'
+ COLLATE 'utf8mb4_general_ci'
");
$connection->close();
}));
diff --git a/src/danog/MadelineProto/Db/MysqlArray.php b/src/danog/MadelineProto/Db/MysqlArray.php
index 9bb5aa96..db048f46 100644
--- a/src/danog/MadelineProto/Db/MysqlArray.php
+++ b/src/danog/MadelineProto/Db/MysqlArray.php
@@ -268,6 +268,9 @@ class MysqlArray implements DbArray
`ts` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`key`)
)
+ ENGINE = InnoDB
+ CHARACTER SET 'utf8mb4'
+ COLLATE 'utf8mb4_general_ci'
");
}
From 6cf1ef504f198f431774c47966c11e33b68a67c8 Mon Sep 17 00:00:00 2001
From: Alexander Pankratov
Date: Sat, 9 May 2020 01:47:12 +0300
Subject: [PATCH 16/29] Always fill usernames cache. Cache fixes.
---
.../MadelineProto/Db/ArrayCacheTrait.php | 35 ++++++++++++-------
src/danog/MadelineProto/Db/MysqlArray.php | 3 ++
.../MTProtoTools/PeerHandler.php | 11 +++---
3 files changed, 33 insertions(+), 16 deletions(-)
diff --git a/src/danog/MadelineProto/Db/ArrayCacheTrait.php b/src/danog/MadelineProto/Db/ArrayCacheTrait.php
index 27bb3f40..dac22356 100644
--- a/src/danog/MadelineProto/Db/ArrayCacheTrait.php
+++ b/src/danog/MadelineProto/Db/ArrayCacheTrait.php
@@ -17,12 +17,15 @@ trait ArrayCacheTrait
*/
protected array $cache = [];
protected string $ttl = '+1 day';
- private string $ttlCheckInterval = '+1 second';
+ private string $ttlCheckInterval = '+1 minute';
private int $nextTtlCheckTs = 0;
protected function getCache(string $key, $default = null)
{
- if ($cacheItem = $this->cache[$key] ?? null) {
+ $cacheItem = $this->cache[$key] ?? null;
+ $this->cleanupCache();
+
+ if ($cacheItem) {
$this->cache[$key]['ttl'] = strtotime($this->ttl);
} else {
return $default;
@@ -39,20 +42,12 @@ trait ArrayCacheTrait
*/
protected function setCache(string $key, $value): void
{
- $now = time();
$this->cache[$key] = [
'value' => $value,
- 'ttl' => $now,
+ 'ttl' => strtotime($this->ttl),
];
- if ($this->nextTtlCheckTs < $now) {
- $this->nextTtlCheckTs = strtotime($this->ttlCheckInterval, $now);
- foreach ($this->cache as $cacheKey => $cacheValue) {
- if ($cacheValue['ttl'] < $now) {
- $this->unsetCache($cacheKey);
- }
- }
- }
+ $this->cleanupCache();
}
/**
@@ -73,4 +68,20 @@ trait ArrayCacheTrait
$this->cache = [];
}
+ /**
+ * Remove all keys from cache
+ */
+ protected function cleanupCache(): void
+ {
+ $now = time();
+ if ($this->nextTtlCheckTs < $now) {
+ $this->nextTtlCheckTs = strtotime($this->ttlCheckInterval, $now);
+ foreach ($this->cache as $cacheKey => $cacheValue) {
+ if ($cacheValue['ttl'] < $now) {
+ $this->unsetCache($cacheKey);
+ }
+ }
+ }
+ }
+
}
\ No newline at end of file
diff --git a/src/danog/MadelineProto/Db/MysqlArray.php b/src/danog/MadelineProto/Db/MysqlArray.php
index db048f46..06db3c84 100644
--- a/src/danog/MadelineProto/Db/MysqlArray.php
+++ b/src/danog/MadelineProto/Db/MysqlArray.php
@@ -142,6 +142,9 @@ class MysqlArray implements DbArray
public function offsetSet($index, $value): void
{
+ if ($this->getCache($index) === $value) {
+ return;
+ }
$this->setCache($index, $value);
$this->request("
diff --git a/src/danog/MadelineProto/MTProtoTools/PeerHandler.php b/src/danog/MadelineProto/MTProtoTools/PeerHandler.php
index 60bcb749..3e3129bc 100644
--- a/src/danog/MadelineProto/MTProtoTools/PeerHandler.php
+++ b/src/danog/MadelineProto/MTProtoTools/PeerHandler.php
@@ -95,6 +95,9 @@ trait PeerHandler
public function addUser(array $user): \Generator
{
$existingChat = yield $this->chats[$user['id']];
+ if ($existingChat) {
+ $this->cacheChatUsername($user['id'], $user);
+ }
if (!isset($user['access_hash']) && !($user['min'] ?? false)) {
if (!empty($existingChat['access_hash'])) {
$this->logger->logger("No access hash with user {$user['id']}, using backup");
@@ -125,9 +128,9 @@ trait PeerHandler
}
}
$this->chats[$user['id']] = $user;
- $this->cacheChatUsername($user['id'], $user);
$this->cachePwrChat($user['id'], false, true);
}
+ $this->cacheChatUsername($user['id'], $user);
break;
case 'userEmpty':
break;
@@ -154,9 +157,9 @@ trait PeerHandler
if (!$existingChat || $existingChat !== $chat) {
$this->logger->logger("Updated chat -{$chat['id']}", \danog\MadelineProto\Logger::ULTRA_VERBOSE);
$this->chats[-$chat['id']] = $chat;
- $this->cacheChatUsername(-$chat['id'], $chat);
$this->cachePwrChat(-$chat['id'], $this->settings['peer']['full_fetch'], true);
}
+ $this->cacheChatUsername(-$chat['id'], $chat);
break;
case 'channelEmpty':
break;
@@ -192,19 +195,19 @@ trait PeerHandler
$chat = $newchat;
}
$this->chats[$bot_api_id] = $chat;
- $this->cacheChatUsername($bot_api_id, $chat);
$fullChat = yield $this->full_chats[$bot_api_id];
if ($this->settings['peer']['full_fetch'] && (!$fullChat || $fullChat['full']['participants_count'] !== (yield from $this->getFullInfo($bot_api_id))['full']['participants_count'])) {
$this->cachePwrChat($bot_api_id, $this->settings['peer']['full_fetch'], true);
}
}
+ $this->cacheChatUsername($bot_api_id, $chat);
break;
}
}
private function cacheChatUsername(int $id, array $chat)
{
- if (!empty($chat['username'])) {
+ if ($id && !empty($chat['username'])) {
$this->usernames[strtolower($chat['username'])] = $id;
}
}
From 8254360d2f52f6423b9bb1390fdd599aa39e19a5 Mon Sep 17 00:00:00 2001
From: Alexander Pankratov
Date: Mon, 11 May 2020 20:28:01 +0300
Subject: [PATCH 17/29] MemoryArray undefined offset fix
---
src/danog/MadelineProto/Db/MemoryArray.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/danog/MadelineProto/Db/MemoryArray.php b/src/danog/MadelineProto/Db/MemoryArray.php
index 18132189..5951a32a 100644
--- a/src/danog/MadelineProto/Db/MemoryArray.php
+++ b/src/danog/MadelineProto/Db/MemoryArray.php
@@ -28,7 +28,7 @@ class MemoryArray extends \ArrayIterator implements DbArray
public function offsetGet($offset): Promise
{
- return call(fn() => parent::offsetGet($offset));
+ return call(fn() => parent::offsetExists($offset) ? parent::offsetGet($offset) : null);
}
public function offsetUnset($offset): Promise
From 912577617ec76a0a69330d8612b619d6d18c9729 Mon Sep 17 00:00:00 2001
From: Alexander Pankratov
Date: Tue, 12 May 2020 01:41:27 +0300
Subject: [PATCH 18/29] Mysql try ... catch
---
src/danog/MadelineProto/Db/Mysql.php | 21 +++++++++++++--------
src/danog/MadelineProto/Db/MysqlArray.php | 2 +-
2 files changed, 14 insertions(+), 9 deletions(-)
diff --git a/src/danog/MadelineProto/Db/Mysql.php b/src/danog/MadelineProto/Db/Mysql.php
index 593a94ab..75d5618e 100644
--- a/src/danog/MadelineProto/Db/Mysql.php
+++ b/src/danog/MadelineProto/Db/Mysql.php
@@ -5,6 +5,7 @@ namespace danog\MadelineProto\Db;
use Amp\Mysql\ConnectionConfig;
use Amp\Mysql\Pool;
use Amp\Sql\Common\ConnectionPool;
+use danog\MadelineProto\Logger;
use function Amp\call;
use function Amp\Mysql\Pool;
use function Amp\Promise\wait;
@@ -62,14 +63,18 @@ class Mysql
private static function createDb(ConnectionConfig $config)
{
wait(call(static function() use($config) {
- $db = $config->getDatabase();
- $connection = pool($config->withDatabase(null));
- yield $connection->query("
- CREATE DATABASE IF NOT EXISTS `{$db}`
- CHARACTER SET 'utf8mb4'
- COLLATE 'utf8mb4_general_ci'
- ");
- $connection->close();
+ try {
+ $db = $config->getDatabase();
+ $connection = pool($config->withDatabase(null));
+ yield $connection->query("
+ CREATE DATABASE IF NOT EXISTS `{$db}`
+ CHARACTER SET 'utf8mb4'
+ COLLATE 'utf8mb4_general_ci'
+ ");
+ $connection->close();
+ } catch (\Throwable $e) {
+ Logger::log($e->getMessage(), Logger::ERROR);
+ }
}));
}
diff --git a/src/danog/MadelineProto/Db/MysqlArray.php b/src/danog/MadelineProto/Db/MysqlArray.php
index 06db3c84..6452629f 100644
--- a/src/danog/MadelineProto/Db/MysqlArray.php
+++ b/src/danog/MadelineProto/Db/MysqlArray.php
@@ -320,7 +320,7 @@ class MysqlArray implements DbArray
try {
$request = yield $this->db->execute($query, $params);
} catch (\Throwable $e) {
- Logger::log($e, Logger::ERROR);
+ Logger::log($e->getMessage(), Logger::ERROR);
return [];
}
From 123e3fadfdcbfaf842fba0abe27cb44174b7be4b Mon Sep 17 00:00:00 2001
From: Alexander Pankratov
Date: Tue, 12 May 2020 15:49:40 +0300
Subject: [PATCH 19/29] Remove unavailable method call
---
src/danog/MadelineProto/Db/MysqlArray.php | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/src/danog/MadelineProto/Db/MysqlArray.php b/src/danog/MadelineProto/Db/MysqlArray.php
index 6452629f..ab267516 100644
--- a/src/danog/MadelineProto/Db/MysqlArray.php
+++ b/src/danog/MadelineProto/Db/MysqlArray.php
@@ -74,10 +74,10 @@ class MysqlArray implements DbArray
foreach ((array) $value as $key => $item) {
$counter++;
if ($counter % 100 === 0) {
- yield $instance->offsetSetAsync($key, $item);
+ yield from $instance->offsetSet($key, $item);
Logger::log("Converting database. $counter/$total", Logger::WARNING);
} else {
- $instance->offsetSetAsync($key, $item);
+ $instance->offsetSet($key, $item);
}
}
@@ -136,18 +136,18 @@ class MysqlArray implements DbArray
*
* @param $value
*
- * @return Promise
+ * @return void
* @throws \Throwable
*/
- public function offsetSet($index, $value): void
+ public function offsetSet($index, $value)
{
if ($this->getCache($index) === $value) {
return;
}
$this->setCache($index, $value);
- $this->request("
+ yield $this->request("
INSERT INTO `{$this->table}`
SET `key` = :index, `value` = :value
ON DUPLICATE KEY UPDATE `value` = :value
From fa1d73009dbf6fde3762ed9c37e775250c6871e1 Mon Sep 17 00:00:00 2001
From: Alexander Pankratov
Date: Tue, 12 May 2020 20:35:15 +0300
Subject: [PATCH 20/29] Add more async requests
---
src/danog/MadelineProto/Db/MysqlArray.php | 48 ++++++++++++-----------
1 file changed, 26 insertions(+), 22 deletions(-)
diff --git a/src/danog/MadelineProto/Db/MysqlArray.php b/src/danog/MadelineProto/Db/MysqlArray.php
index ab267516..021c470d 100644
--- a/src/danog/MadelineProto/Db/MysqlArray.php
+++ b/src/danog/MadelineProto/Db/MysqlArray.php
@@ -7,7 +7,6 @@ use Amp\Mysql\Pool;
use Amp\Producer;
use Amp\Promise;
use Amp\Sql\ResultSet;
-use danog\MadelineProto\Exception;
use danog\MadelineProto\Logger;
use function Amp\call;
use function Amp\Promise\wait;
@@ -50,19 +49,20 @@ class MysqlArray implements DbArray
$instance->db = static::getDbConnection($settings);
$instance->ttl = $settings['cache_ttl'] ?? $instance->ttl;
- if ($value instanceof static && $value->table) {
- if (
- mb_strpos($value->table, 'tmp') === 0 &&
- mb_strpos($instance->table, 'tmp') !== 0
- ) {
- $instance->renameTable($value->table, $instance->table);
- } elseif (mb_strpos($instance->table, 'tmp') === 0){
- $instance->table = $value->table;
- }
- }
- $instance->prepareTable();
-
Loop::defer(static function() use($value, $instance) {
+ if ($value instanceof static && $value->table) {
+ if (
+ mb_strpos($value->table, 'tmp') === 0 &&
+ mb_strpos($instance->table, 'tmp') !== 0
+ ) {
+ yield from $instance->renameTable($value->table, $instance->table);
+ } elseif (mb_strpos($instance->table, 'tmp') === 0){
+ $instance->table = $value->table;
+ }
+ }
+
+ yield from $instance->prepareTable();
+
if (!empty($value) && !$value instanceof static) {
Logger::log('Converting database.', Logger::ERROR);
if ($value instanceof DbArray) {
@@ -74,7 +74,7 @@ class MysqlArray implements DbArray
foreach ((array) $value as $key => $item) {
$counter++;
if ($counter % 100 === 0) {
- yield from $instance->offsetSet($key, $item);
+ yield $instance->offsetSet($key, $item);
Logger::log("Converting database. $counter/$total", Logger::WARNING);
} else {
$instance->offsetSet($key, $item);
@@ -136,18 +136,17 @@ class MysqlArray implements DbArray
*
* @param $value
*
- * @return void
* @throws \Throwable
*/
- public function offsetSet($index, $value)
+ public function offsetSet($index, $value): Promise
{
if ($this->getCache($index) === $value) {
- return;
+ return call(fn()=>null);
}
$this->setCache($index, $value);
- yield $this->request("
+ return $this->request("
INSERT INTO `{$this->table}`
SET `key` = :index, `value` = :value
ON DUPLICATE KEY UPDATE `value` = :value
@@ -263,7 +262,7 @@ class MysqlArray implements DbArray
*/
private function prepareTable()
{
- return $this->syncRequest("
+ return yield $this->request("
CREATE TABLE IF NOT EXISTS `{$this->table}`
(
`key` VARCHAR(255) NOT NULL,
@@ -279,10 +278,11 @@ class MysqlArray implements DbArray
private function renameTable(string $from, string $to)
{
- $this->syncRequest("
+ yield $this->request("
ALTER TABLE {$from} RENAME TO {$to};
");
- $this->syncRequest("
+
+ yield $this->request("
DROP TABLE IF EXISTS {$from};
");
}
@@ -313,7 +313,11 @@ class MysqlArray implements DbArray
private function request(string $query, array $params = []): Promise
{
return call(function() use($query, $params) {
- if (empty($this->db)) {
+
+ Logger::log([$query, $params], Logger::VERBOSE);
+
+ if (empty($this->db) || !$this->db->isAlive()) {
+ Logger::log('No database connection', Logger::WARNING);
return [];
}
From 9016a7c0dd88ed6049e25874918ddf7a696b4767 Mon Sep 17 00:00:00 2001
From: Alexander Pankratov
Date: Sun, 17 May 2020 00:38:30 +0300
Subject: [PATCH 21/29] Allow dots in mysql table names
---
src/danog/MadelineProto/Db/MysqlArray.php | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/src/danog/MadelineProto/Db/MysqlArray.php b/src/danog/MadelineProto/Db/MysqlArray.php
index 021c470d..565bc080 100644
--- a/src/danog/MadelineProto/Db/MysqlArray.php
+++ b/src/danog/MadelineProto/Db/MysqlArray.php
@@ -114,7 +114,7 @@ class MysqlArray implements DbArray
}
$row = yield $this->request(
- "SELECT `value` FROM {$this->table} WHERE `key` = :index LIMIT 1",
+ "SELECT `value` FROM `{$this->table}` WHERE `key` = :index LIMIT 1",
['index' => $offset]
);
@@ -192,7 +192,7 @@ class MysqlArray implements DbArray
*/
public function getArrayCopy(): array
{
- $rows = $this->syncRequest("SELECT `key`, `value` FROM {$this->table}");
+ $rows = $this->syncRequest("SELECT `key`, `value` FROM `{$this->table}`");
$result = [];
foreach ($rows as $row) {
$result[$row['key']] = $this->getValue($row);
@@ -204,7 +204,7 @@ class MysqlArray implements DbArray
public function getIterator(): Producer
{
return new Producer(function (callable $emit) {
- $request = yield $this->db->execute("SELECT `key`, `value` FROM {$this->table}");
+ $request = yield $this->db->execute("SELECT `key`, `value` FROM `{$this->table}`");
while (yield $request->advance()) {
$row = $request->getCurrent();
@@ -225,7 +225,7 @@ class MysqlArray implements DbArray
public function count(): Promise
{
return call(function(){
- $row = yield $this->request("SELECT count(`key`) as `count` FROM {$this->table}");
+ $row = yield $this->request("SELECT count(`key`) as `count` FROM `{$this->table}`");
return $row[0]['count'] ?? 0;
});
}
@@ -279,11 +279,11 @@ class MysqlArray implements DbArray
private function renameTable(string $from, string $to)
{
yield $this->request("
- ALTER TABLE {$from} RENAME TO {$to};
+ ALTER TABLE `{$from}` RENAME TO `{$to}`;
");
yield $this->request("
- DROP TABLE IF EXISTS {$from};
+ DROP TABLE IF EXISTS `{$from}`;
");
}
From fea71efc99a738d6300372699f74935fd37a161a Mon Sep 17 00:00:00 2001
From: Alexander Pankratov
Date: Sun, 17 May 2020 22:04:32 +0300
Subject: [PATCH 22/29] Upgrade for MysqlArray initialization
---
.../MadelineProto/Db/DbPropertiesFabric.php | 5 +-
src/danog/MadelineProto/Db/DbType.php | 12 ++-
src/danog/MadelineProto/Db/MemoryArray.php | 12 +--
src/danog/MadelineProto/Db/MysqlArray.php | 78 +++++++++++--------
src/danog/MadelineProto/MTProto.php | 2 +-
5 files changed, 66 insertions(+), 43 deletions(-)
diff --git a/src/danog/MadelineProto/Db/DbPropertiesFabric.php b/src/danog/MadelineProto/Db/DbPropertiesFabric.php
index cc2dce8c..40c75d92 100644
--- a/src/danog/MadelineProto/Db/DbPropertiesFabric.php
+++ b/src/danog/MadelineProto/Db/DbPropertiesFabric.php
@@ -2,6 +2,7 @@
namespace danog\MadelineProto\Db;
+use Amp\Promise;
use danog\MadelineProto\MTProto;
class DbPropertiesFabric
@@ -12,13 +13,13 @@ class DbPropertiesFabric
* @param string $name
* @param $value
*
- * @return DbType
+ * @return Promise
*
* @uses \danog\MadelineProto\Db\MemoryArray
* @uses \danog\MadelineProto\Db\SharedMemoryArray
* @uses \danog\MadelineProto\Db\MysqlArray
*/
- public static function get(MTProto $madelineProto, string $propertyType, string $name, $value = null): DbType
+ public static function get(MTProto $madelineProto, string $propertyType, string $name, $value = null): Promise
{
$class = __NAMESPACE__;
$dbSettings = $madelineProto->settings['db'];
diff --git a/src/danog/MadelineProto/Db/DbType.php b/src/danog/MadelineProto/Db/DbType.php
index f816a778..19f15e04 100644
--- a/src/danog/MadelineProto/Db/DbType.php
+++ b/src/danog/MadelineProto/Db/DbType.php
@@ -2,7 +2,17 @@
namespace danog\MadelineProto\Db;
+use Amp\Promise;
+
interface DbType
{
- static function getInstance(string $name, $value = null, string $tablePrefix = '', array $settings = []): self;
+ /**
+ * @param string $name
+ * @param null $value
+ * @param string $tablePrefix
+ * @param array $settings
+ *
+ * @return Promise
+ */
+ static function getInstance(string $name, $value = null, string $tablePrefix = '', array $settings = []): Promise;
}
\ No newline at end of file
diff --git a/src/danog/MadelineProto/Db/MemoryArray.php b/src/danog/MadelineProto/Db/MemoryArray.php
index 5951a32a..a7f65584 100644
--- a/src/danog/MadelineProto/Db/MemoryArray.php
+++ b/src/danog/MadelineProto/Db/MemoryArray.php
@@ -13,12 +13,14 @@ class MemoryArray extends \ArrayIterator implements DbArray
parent::__construct((array) $array, $flags | self::STD_PROP_LIST);
}
- public static function getInstance(string $name, $value = null, string $tablePrefix = '', array $settings = []): DbArray
+ public static function getInstance(string $name, $value = null, string $tablePrefix = '', array $settings = []): Promise
{
- if ($value instanceof DbArray) {
- $value = $value->getArrayCopy();
- }
- return new static($value);
+ return call(function() use ($value) {
+ if ($value instanceof DbArray) {
+ $value = $value->getArrayCopy();
+ }
+ return new static($value);
+ });
}
public function offsetExists($offset): Promise
diff --git a/src/danog/MadelineProto/Db/MysqlArray.php b/src/danog/MadelineProto/Db/MysqlArray.php
index 565bc080..85c14e00 100644
--- a/src/danog/MadelineProto/Db/MysqlArray.php
+++ b/src/danog/MadelineProto/Db/MysqlArray.php
@@ -40,7 +40,7 @@ class MysqlArray implements DbArray
}
- public static function getInstance(string $name, $value = null, string $tablePrefix = '', array $settings = []): DbType
+ public static function getInstance(string $name, $value = null, string $tablePrefix = '', array $settings = []): Promise
{
$instance = new static();
@@ -49,43 +49,51 @@ class MysqlArray implements DbArray
$instance->db = static::getDbConnection($settings);
$instance->ttl = $settings['cache_ttl'] ?? $instance->ttl;
- Loop::defer(static function() use($value, $instance) {
- if ($value instanceof static && $value->table) {
- if (
- mb_strpos($value->table, 'tmp') === 0 &&
- mb_strpos($instance->table, 'tmp') !== 0
- ) {
- yield from $instance->renameTable($value->table, $instance->table);
- } elseif (mb_strpos($instance->table, 'tmp') === 0){
- $instance->table = $value->table;
- }
- }
-
+ return call(static function() use($instance, $value) {
+ yield from static::renameTmpTable($instance, $value);
yield from $instance->prepareTable();
+ Loop::defer(fn() => static::migrateDataToDb($instance, $value));
- if (!empty($value) && !$value instanceof static) {
- Logger::log('Converting database.', Logger::ERROR);
- if ($value instanceof DbArray) {
- $value = $value->getArrayCopy();
- }
- $value = (array) $value;
- $counter = 0;
- $total = count($value);
- foreach ((array) $value as $key => $item) {
- $counter++;
- if ($counter % 100 === 0) {
- yield $instance->offsetSet($key, $item);
- Logger::log("Converting database. $counter/$total", Logger::WARNING);
- } else {
- $instance->offsetSet($key, $item);
- }
-
- }
- Logger::log('Converting database done.', Logger::ERROR);
- }
+ return $instance;
});
+ }
- return $instance;
+ private static function renameTmpTable(MysqlArray $instance, ?DbArray $value): \Generator
+ {
+ if ($value instanceof static && $value->table) {
+ if (
+ mb_strpos($value->table, 'tmp') === 0 &&
+ mb_strpos($instance->table, 'tmp') !== 0
+ ) {
+ yield from $instance->renameTable($value->table, $instance->table);
+ } elseif (mb_strpos($instance->table, 'tmp') === 0) {
+ $instance->table = $value->table;
+ }
+ }
+ }
+
+ private static function migrateDataToDb(MysqlArray $instance, ?DbArray $value): \Generator
+ {
+ if (!empty($value) && !$value instanceof static) {
+ Logger::log('Converting database.', Logger::ERROR);
+ if ($value instanceof DbArray) {
+ $value = $value->getArrayCopy();
+ }
+ $value = (array) $value;
+ $counter = 0;
+ $total = count($value);
+ foreach ((array) $value as $key => $item) {
+ $counter++;
+ if ($counter % 100 === 0) {
+ yield $instance->offsetSet($key, $item);
+ Logger::log("Converting database. $counter/$total", Logger::WARNING);
+ } else {
+ $instance->offsetSet($key, $item);
+ }
+
+ }
+ Logger::log('Converting database done.', Logger::ERROR);
+ }
}
/**
@@ -262,6 +270,7 @@ class MysqlArray implements DbArray
*/
private function prepareTable()
{
+ Logger::log("Creating/checking table {$this->table}", Logger::WARNING);
return yield $this->request("
CREATE TABLE IF NOT EXISTS `{$this->table}`
(
@@ -278,6 +287,7 @@ class MysqlArray implements DbArray
private function renameTable(string $from, string $to)
{
+ Logger::log("Renaming table {$from} to {$to}", Logger::WARNING);
yield $this->request("
ALTER TABLE `{$from}` RENAME TO `{$to}`;
");
diff --git a/src/danog/MadelineProto/MTProto.php b/src/danog/MadelineProto/MTProto.php
index d9e15291..e71ad4fe 100644
--- a/src/danog/MadelineProto/MTProto.php
+++ b/src/danog/MadelineProto/MTProto.php
@@ -572,7 +572,7 @@ class MTProto extends AsyncConstruct implements TLCallback
if ($reset) {
unset($this->{$property});
} else {
- $this->{$property} = DbPropertiesFabric::get($this, $type, $property, $this->{$property});
+ $this->{$property} = yield DbPropertiesFabric::get($this, $type, $property, $this->{$property});
}
}
From d0e57f2535be021621032d7c63592e2e4f32c8ed Mon Sep 17 00:00:00 2001
From: Alexander Pankratov
Date: Sun, 17 May 2020 22:04:58 +0300
Subject: [PATCH 23/29] Decrease default cache_ttl
---
src/danog/MadelineProto/Db/ArrayCacheTrait.php | 2 +-
src/danog/MadelineProto/MTProto.php | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/danog/MadelineProto/Db/ArrayCacheTrait.php b/src/danog/MadelineProto/Db/ArrayCacheTrait.php
index dac22356..66ed3ad7 100644
--- a/src/danog/MadelineProto/Db/ArrayCacheTrait.php
+++ b/src/danog/MadelineProto/Db/ArrayCacheTrait.php
@@ -16,7 +16,7 @@ trait ArrayCacheTrait
* @var array
*/
protected array $cache = [];
- protected string $ttl = '+1 day';
+ protected string $ttl = '+5 minutes';
private string $ttlCheckInterval = '+1 minute';
private int $nextTtlCheckTs = 0;
diff --git a/src/danog/MadelineProto/MTProto.php b/src/danog/MadelineProto/MTProto.php
index e71ad4fe..8656aaa9 100644
--- a/src/danog/MadelineProto/MTProto.php
+++ b/src/danog/MadelineProto/MTProto.php
@@ -1323,7 +1323,7 @@ class MTProto extends AsyncConstruct implements TLCallback
'database' => 'MadelineProto', //will be created automatically
'max_connections' => 10,
'idle_timeout' => 60,
- 'cache_ttl' => '+1 day', //keep records in memory after last read
+ 'cache_ttl' => '+5 minutes', //keep records in memory after last read
]
],
'upload' => ['allow_automatic_upload' => true, 'part_size' => 512 * 1024, 'parallel_chunks' => 20], 'download' => ['report_broken_media' => true, 'part_size' => 1024 * 1024, 'parallel_chunks' => 20], 'pwr' => [
From 7c1b602779328877a6f988167f298370e0260f81 Mon Sep 17 00:00:00 2001
From: Alexander Pankratov
Date: Mon, 18 May 2020 18:50:47 +0300
Subject: [PATCH 24/29] Fix Undefined index: value in ArrayCacheTrait.php:34
---
.../MadelineProto/Db/ArrayCacheTrait.php | 29 ++++++++++++-------
1 file changed, 18 insertions(+), 11 deletions(-)
diff --git a/src/danog/MadelineProto/Db/ArrayCacheTrait.php b/src/danog/MadelineProto/Db/ArrayCacheTrait.php
index 66ed3ad7..f94a90eb 100644
--- a/src/danog/MadelineProto/Db/ArrayCacheTrait.php
+++ b/src/danog/MadelineProto/Db/ArrayCacheTrait.php
@@ -23,15 +23,16 @@ trait ArrayCacheTrait
protected function getCache(string $key, $default = null)
{
$cacheItem = $this->cache[$key] ?? null;
- $this->cleanupCache();
+ $result = $default;
- if ($cacheItem) {
+ if (\is_array($cacheItem)) {
+ $result = $cacheItem['value'];
$this->cache[$key]['ttl'] = strtotime($this->ttl);
- } else {
- return $default;
}
- return $cacheItem['value'];
+ $this->cleanupCache();
+
+ return $result;
}
/**
@@ -74,14 +75,20 @@ trait ArrayCacheTrait
protected function cleanupCache(): void
{
$now = time();
- if ($this->nextTtlCheckTs < $now) {
- $this->nextTtlCheckTs = strtotime($this->ttlCheckInterval, $now);
- foreach ($this->cache as $cacheKey => $cacheValue) {
- if ($cacheValue['ttl'] < $now) {
- $this->unsetCache($cacheKey);
- }
+ if ($this->nextTtlCheckTs > $now) {
+ return;
+ }
+
+ $this->nextTtlCheckTs = strtotime($this->ttlCheckInterval, $now);
+ $oldKeys = [];
+ foreach ($this->cache as $cacheKey => $cacheValue) {
+ if ($cacheValue['ttl'] < $now) {
+ $oldKeys[] = $cacheKey;
}
}
+ foreach ($oldKeys as $oldKey) {
+ $this->unsetCache($oldKey);
+ }
}
}
\ No newline at end of file
From e0832390ce83bdf7707c2f2a9f722cb557543af2 Mon Sep 17 00:00:00 2001
From: Alexander Pankratov
Date: Wed, 20 May 2020 23:15:52 +0300
Subject: [PATCH 25/29] Type fixes
---
src/danog/MadelineProto/Db/MysqlArray.php | 29 +++++++++++++++++++----
1 file changed, 24 insertions(+), 5 deletions(-)
diff --git a/src/danog/MadelineProto/Db/MysqlArray.php b/src/danog/MadelineProto/Db/MysqlArray.php
index 85c14e00..801a680b 100644
--- a/src/danog/MadelineProto/Db/MysqlArray.php
+++ b/src/danog/MadelineProto/Db/MysqlArray.php
@@ -40,6 +40,14 @@ class MysqlArray implements DbArray
}
+ /**
+ * @param string $name
+ * @param DbArray|array|null $value
+ * @param string $tablePrefix
+ * @param array $settings
+ *
+ * @return Promise
+ */
public static function getInstance(string $name, $value = null, string $tablePrefix = '', array $settings = []): Promise
{
$instance = new static();
@@ -58,7 +66,13 @@ class MysqlArray implements DbArray
});
}
- private static function renameTmpTable(MysqlArray $instance, ?DbArray $value): \Generator
+ /**
+ * @param MysqlArray $instance
+ * @param DbArray|array|null $value
+ *
+ * @return \Generator
+ */
+ private static function renameTmpTable(MysqlArray $instance, $value): \Generator
{
if ($value instanceof static && $value->table) {
if (
@@ -72,13 +86,18 @@ class MysqlArray implements DbArray
}
}
- private static function migrateDataToDb(MysqlArray $instance, ?DbArray $value): \Generator
+ /**
+ * @param MysqlArray $instance
+ * @param DbArray|array|null $value
+ *
+ * @return \Generator
+ * @throws \Throwable
+ */
+ private static function migrateDataToDb(MysqlArray $instance, $value): \Generator
{
if (!empty($value) && !$value instanceof static) {
Logger::log('Converting database.', Logger::ERROR);
- if ($value instanceof DbArray) {
- $value = $value->getArrayCopy();
- }
+
$value = (array) $value;
$counter = 0;
$total = count($value);
From 1e23970ab3e4413bb79a10885482c38ee2f3a28c Mon Sep 17 00:00:00 2001
From: Alexander Pankratov
Date: Sun, 7 Jun 2020 01:00:44 +0300
Subject: [PATCH 26/29] DbArray refactoring and convertation improvement
---
src/danog/MadelineProto/Db/DbArray.php | 2 +-
.../MadelineProto/Db/DbPropertiesFabric.php | 21 ++-----
.../MadelineProto/Db/DbPropertiesTrait.php | 52 +++++++++++++++++
src/danog/MadelineProto/Db/MemoryArray.php | 13 +++--
src/danog/MadelineProto/Db/MysqlArray.php | 57 ++++++++-----------
src/danog/MadelineProto/MTProto.php | 33 ++---------
6 files changed, 96 insertions(+), 82 deletions(-)
create mode 100644 src/danog/MadelineProto/Db/DbPropertiesTrait.php
diff --git a/src/danog/MadelineProto/Db/DbArray.php b/src/danog/MadelineProto/Db/DbArray.php
index edfaf2b3..b7d68586 100644
--- a/src/danog/MadelineProto/Db/DbArray.php
+++ b/src/danog/MadelineProto/Db/DbArray.php
@@ -7,7 +7,7 @@ use Amp\Promise;
interface DbArray extends DbType, \ArrayAccess, \Countable
{
- public function getArrayCopy(): array;
+ public function getArrayCopy(): Promise;
public function offsetExists($offset): Promise;
public function offsetGet($offset): Promise;
public function offsetSet($offset, $value);
diff --git a/src/danog/MadelineProto/Db/DbPropertiesFabric.php b/src/danog/MadelineProto/Db/DbPropertiesFabric.php
index 40c75d92..7e85f5db 100644
--- a/src/danog/MadelineProto/Db/DbPropertiesFabric.php
+++ b/src/danog/MadelineProto/Db/DbPropertiesFabric.php
@@ -3,12 +3,12 @@
namespace danog\MadelineProto\Db;
use Amp\Promise;
-use danog\MadelineProto\MTProto;
class DbPropertiesFabric
{
/**
- * @param MTProto $madelineProto
+ * @param array $dbSettings
+ * @param string $namePrefix
* @param string $propertyType
* @param string $name
* @param $value
@@ -19,10 +19,10 @@ class DbPropertiesFabric
* @uses \danog\MadelineProto\Db\SharedMemoryArray
* @uses \danog\MadelineProto\Db\MysqlArray
*/
- public static function get(MTProto $madelineProto, string $propertyType, string $name, $value = null): Promise
+ public static function get(array $dbSettings, string $namePrefix, string $propertyType, string $name, $value = null): Promise
{
$class = __NAMESPACE__;
- $dbSettings = $madelineProto->settings['db'];
+
switch (strtolower($dbSettings['type'])) {
case 'memory':
$class .= '\Memory';
@@ -44,18 +44,7 @@ class DbPropertiesFabric
throw new \InvalidArgumentException("Unknown $propertyType: {$propertyType}");
}
- $prefix = static::getSessionId($madelineProto);
- return $class::getInstance($name, $value, $prefix, $dbSettings[$dbSettings['type']]??[]);
- }
-
- private static function getSessionId(MTProto $madelineProto): string
- {
- $result = $madelineProto->getSelf()['id'] ?? null;
- if (!$result) {
- $result = 'tmp_';
- $result .= str_replace('0','', spl_object_hash($madelineProto));
- }
- return (string) $result;
+ return $class::getInstance($name, $value, $namePrefix, $dbSettings[$dbSettings['type']]??[]);
}
}
\ No newline at end of file
diff --git a/src/danog/MadelineProto/Db/DbPropertiesTrait.php b/src/danog/MadelineProto/Db/DbPropertiesTrait.php
new file mode 100644
index 00000000..4c5a6f33
--- /dev/null
+++ b/src/danog/MadelineProto/Db/DbPropertiesTrait.php
@@ -0,0 +1,52 @@
+dbProperies)) {
+ throw new \LogicException(__CLASS__ . ' must have a $dbProperies');
+ }
+ $dbSettings = $MadelineProto->settings['db'];
+ $prefix = static::getSessionId($MadelineProto);
+
+ foreach ($this->dbProperies as $property => $type) {
+ if ($reset) {
+ unset($this->{$property});
+ } else {
+ $this->{$property} = yield DbPropertiesFabric::get($dbSettings, $prefix, $type, $property, $this->{$property});
+ }
+ }
+
+ if (!$reset && yield $this->usernames->count() === 0) {
+ $this->logger('Filling database cache. This can take few minutes.', Logger::WARNING);
+ $iterator = $this->chats->getIterator();
+ while (yield $iterator->advance()) {
+ [$id, $chat] = $iterator->getCurrent();
+ if (isset($chat['username'])) {
+ $this->usernames[\strtolower($chat['username'])] = $this->getId($chat);
+ }
+ }
+ $this->logger('Cache filled.', Logger::WARNING);
+ }
+ }
+
+ private static function getSessionId(MTProto $madelineProto): string
+ {
+ $result = $madelineProto->getSelf()['id'] ?? null;
+ if (!$result) {
+ $result = 'tmp_';
+ $result .= str_replace('0','', spl_object_hash($madelineProto));
+ }
+
+ $className = explode('\\',__CLASS__);
+ $result .= '_' . end($className);
+ return $result;
+ }
+}
\ No newline at end of file
diff --git a/src/danog/MadelineProto/Db/MemoryArray.php b/src/danog/MadelineProto/Db/MemoryArray.php
index a7f65584..24f319c3 100644
--- a/src/danog/MadelineProto/Db/MemoryArray.php
+++ b/src/danog/MadelineProto/Db/MemoryArray.php
@@ -4,6 +4,7 @@ namespace danog\MadelineProto\Db;
use Amp\Producer;
use Amp\Promise;
+use danog\MadelineProto\Logger;
use function Amp\call;
class MemoryArray extends \ArrayIterator implements DbArray
@@ -15,9 +16,13 @@ class MemoryArray extends \ArrayIterator implements DbArray
public static function getInstance(string $name, $value = null, string $tablePrefix = '', array $settings = []): Promise
{
- return call(function() use ($value) {
+ return call(static function() use ($value) {
+ if ($value instanceof MemoryArray) {
+ return $value;
+ }
if ($value instanceof DbArray) {
- $value = $value->getArrayCopy();
+ Logger::log("Loading database to memory. Please wait.", Logger::WARNING);
+ $value = yield $value->getArrayCopy();
}
return new static($value);
});
@@ -43,9 +48,9 @@ class MemoryArray extends \ArrayIterator implements DbArray
return call(fn() => parent::count());
}
- public function getArrayCopy(): array
+ public function getArrayCopy(): Promise
{
- return parent::getArrayCopy();
+ return call(fn() => parent::getArrayCopy());
}
public function getIterator(): Producer
diff --git a/src/danog/MadelineProto/Db/MysqlArray.php b/src/danog/MadelineProto/Db/MysqlArray.php
index 801a680b..8c842a7a 100644
--- a/src/danog/MadelineProto/Db/MysqlArray.php
+++ b/src/danog/MadelineProto/Db/MysqlArray.php
@@ -60,7 +60,7 @@ class MysqlArray implements DbArray
return call(static function() use($instance, $value) {
yield from static::renameTmpTable($instance, $value);
yield from $instance->prepareTable();
- Loop::defer(fn() => static::migrateDataToDb($instance, $value));
+ yield from static::migrateDataToDb($instance, $value);
return $instance;
});
@@ -76,11 +76,11 @@ class MysqlArray implements DbArray
{
if ($value instanceof static && $value->table) {
if (
- mb_strpos($value->table, 'tmp') === 0 &&
+ $value->table !== $instance->table &&
mb_strpos($instance->table, 'tmp') !== 0
) {
yield from $instance->renameTable($value->table, $instance->table);
- } elseif (mb_strpos($instance->table, 'tmp') === 0) {
+ } else {
$instance->table = $value->table;
}
}
@@ -95,17 +95,21 @@ class MysqlArray implements DbArray
*/
private static function migrateDataToDb(MysqlArray $instance, $value): \Generator
{
- if (!empty($value) && !$value instanceof static) {
+ if (!empty($value) && !$value instanceof MysqlArray) {
Logger::log('Converting database.', Logger::ERROR);
- $value = (array) $value;
+ if ($value instanceof DbArray) {
+ $value = yield $value->getArrayCopy();
+ } else {
+ $value = (array) $value;
+ }
$counter = 0;
$total = count($value);
- foreach ((array) $value as $key => $item) {
+ foreach ($value as $key => $item) {
$counter++;
if ($counter % 100 === 0) {
yield $instance->offsetSet($key, $item);
- Logger::log("Converting database. $counter/$total", Logger::WARNING);
+ Logger::log("Loading data to table {$instance->table}: $counter/$total", Logger::WARNING);
} else {
$instance->offsetSet($key, $item);
}
@@ -212,20 +216,20 @@ class MysqlArray implements DbArray
/**
* Get array copy
*
- * @link https://php.net/manual/en/arrayiterator.getarraycopy.php
- * @return array A copy of the array, or array of public properties
- * if ArrayIterator refers to an object.
+ * @return Promise
* @throws \Throwable
*/
- public function getArrayCopy(): array
+ public function getArrayCopy(): Promise
{
- $rows = $this->syncRequest("SELECT `key`, `value` FROM `{$this->table}`");
- $result = [];
- foreach ($rows as $row) {
- $result[$row['key']] = $this->getValue($row);
- }
-
- return $result;
+ return call(function(){
+ $iterator = $this->getIterator();
+ $result = [];
+ while (yield $iterator->advance()) {
+ [$key, $value] = $iterator->getCurrent();
+ $result[$key] = $value;
+ }
+ return $result;
+ });
}
public function getIterator(): Producer
@@ -235,7 +239,6 @@ class MysqlArray implements DbArray
while (yield $request->advance()) {
$row = $request->getCurrent();
-
yield $emit([$row['key'], $this->getValue($row)]);
}
});
@@ -317,21 +320,7 @@ class MysqlArray implements DbArray
}
/**
- * Perform blocking request to db
- *
- * @param string $query
- * @param array $params
- *
- * @return array|null
- * @throws \Throwable
- */
- private function syncRequest(string $query, array $params = []): array
- {
- return wait($this->request($query, $params));
- }
-
- /**
- * Perform blocking request to db
+ * Perform async request to db
*
* @param string $query
* @param array $params
diff --git a/src/danog/MadelineProto/MTProto.php b/src/danog/MadelineProto/MTProto.php
index 8656aaa9..aeb69538 100644
--- a/src/danog/MadelineProto/MTProto.php
+++ b/src/danog/MadelineProto/MTProto.php
@@ -26,6 +26,7 @@ use Amp\Promise;
use danog\MadelineProto\Async\AsyncConstruct;
use danog\MadelineProto\Db\DbArray;
use danog\MadelineProto\Db\DbPropertiesFabric;
+use danog\MadelineProto\Db\DbPropertiesTrait;
use danog\MadelineProto\Db\Mysql;
use danog\MadelineProto\Loop\Generic\PeriodicLoop;
use danog\MadelineProto\Loop\Update\FeedLoop;
@@ -72,6 +73,7 @@ class MTProto extends AsyncConstruct implements TLCallback
use \danog\MadelineProto\Wrappers\Start;
use \danog\MadelineProto\Wrappers\Templates;
use \danog\MadelineProto\Wrappers\TOS;
+ use DbPropertiesTrait;
/**
* Old internal version of MadelineProto.
*
@@ -424,7 +426,7 @@ class MTProto extends AsyncConstruct implements TLCallback
* @see DbPropertiesFabric
* @var array
*/
- private array $dbProperies = [
+ protected array $dbProperies = [
'chats' => 'array',
'full_chats' => 'array',
'channel_participants' => 'array',
@@ -566,29 +568,6 @@ class MTProto extends AsyncConstruct implements TLCallback
];
}
- public function initDb(bool $reset = false): \Generator
- {
- foreach ($this->dbProperies as $property => $type) {
- if ($reset) {
- unset($this->{$property});
- } else {
- $this->{$property} = yield DbPropertiesFabric::get($this, $type, $property, $this->{$property});
- }
- }
-
- if (!$reset && yield $this->usernames->count() === 0) {
- $this->logger('Filling database cache. This can take few minutes.', Logger::WARNING);
- $iterator = $this->chats->getIterator();
- while (yield $iterator->advance()) {
- [$id, $chat] = $iterator->getCurrent();
- if (isset($chat['username'])) {
- $this->usernames[\strtolower($chat['username'])] = $this->getId($chat);
- }
- }
- $this->logger('Cache filled.', Logger::WARNING);
- }
- }
-
/**
* Cleanup memory and session file.
*
@@ -801,7 +780,7 @@ class MTProto extends AsyncConstruct implements TLCallback
$this->TL->init($this->settings['tl_schema']['src'], $callbacks);
}
- yield from $this->initDb();
+ yield from $this->initDb($this);
}
@@ -834,7 +813,7 @@ class MTProto extends AsyncConstruct implements TLCallback
unset($settings['authorization']['rsa_key']);
}
- yield from $this->initDb();
+ yield from $this->initDb($this);
if (!isset($this->secret_chats)) {
$this->secret_chats = [];
@@ -1552,7 +1531,7 @@ class MTProto extends AsyncConstruct implements TLCallback
$this->updates = [];
$this->secret_chats = [];
- yield from $this->initDb(true);
+ yield from $this->initDb($this,true);
$this->tos = ['expires' => 0, 'accepted' => true];
$this->referenceDatabase = new ReferenceDatabase($this);
From 2aab5e8cc8e02666d8089b48434dbc8580b0916d Mon Sep 17 00:00:00 2001
From: Alexander Pankratov
Date: Sun, 7 Jun 2020 21:56:54 +0300
Subject: [PATCH 27/29] getParticipantsHash generator fix
---
src/danog/MadelineProto/MTProtoTools/PeerHandler.php | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/danog/MadelineProto/MTProtoTools/PeerHandler.php b/src/danog/MadelineProto/MTProtoTools/PeerHandler.php
index 3e3129bc..461c67d4 100644
--- a/src/danog/MadelineProto/MTProtoTools/PeerHandler.php
+++ b/src/danog/MadelineProto/MTProtoTools/PeerHandler.php
@@ -916,7 +916,7 @@ trait PeerHandler
$last_count = -1;
do {
try {
- $gres = yield from $this->methodCallAsyncRead('channels.getParticipants', ['channel' => $channel, 'filter' => ['_' => $filter, 'q' => $q], 'offset' => $offset, 'limit' => $limit, 'hash' => $hash = $this->getParticipantsHash($channel, $filter, $q, $offset, $limit)], ['datacenter' => $this->datacenter->curdc, 'heavy' => true]);
+ $gres = yield from $this->methodCallAsyncRead('channels.getParticipants', ['channel' => $channel, 'filter' => ['_' => $filter, 'q' => $q], 'offset' => $offset, 'limit' => $limit, 'hash' => $hash = yield from $this->getParticipantsHash($channel, $filter, $q, $offset, $limit)], ['datacenter' => $this->datacenter->curdc, 'heavy' => true]);
} catch (\danog\MadelineProto\RPCErrorException $e) {
if ($e->rpc === 'CHAT_ADMIN_REQUIRED') {
$this->logger->logger($e->rpc);
@@ -1002,7 +1002,7 @@ trait PeerHandler
$participant[$filter][$q][$offset][$limit] = $gres;
$this->channel_participants[$channel['channel_id']] = $participant;
}
- private function getParticipantsHash($channel, $filter, $q, $offset, $limit)
+ private function getParticipantsHash($channel, $filter, $q, $offset, $limit): \Generator
{
return (yield $this->channel_participants[$channel['channel_id']])[$filter][$q][$offset][$limit]['hash'] ?? 0;
}
From ca03bc662a87727a8388b65a132b82c06d4ea58b Mon Sep 17 00:00:00 2001
From: Alexander Pankratov
Date: Tue, 9 Jun 2020 01:47:54 +0300
Subject: [PATCH 28/29] New mysql cache cleanup
---
.../MadelineProto/Db/ArrayCacheTrait.php | 28 +++++++++----------
src/danog/MadelineProto/Db/MysqlArray.php | 2 ++
2 files changed, 15 insertions(+), 15 deletions(-)
diff --git a/src/danog/MadelineProto/Db/ArrayCacheTrait.php b/src/danog/MadelineProto/Db/ArrayCacheTrait.php
index f94a90eb..20abdb9c 100644
--- a/src/danog/MadelineProto/Db/ArrayCacheTrait.php
+++ b/src/danog/MadelineProto/Db/ArrayCacheTrait.php
@@ -2,6 +2,9 @@
namespace danog\MadelineProto\Db;
+use Amp\Loop;
+use danog\MadelineProto\Logger;
+
trait ArrayCacheTrait
{
/**
@@ -18,7 +21,6 @@ trait ArrayCacheTrait
protected array $cache = [];
protected string $ttl = '+5 minutes';
private string $ttlCheckInterval = '+1 minute';
- private int $nextTtlCheckTs = 0;
protected function getCache(string $key, $default = null)
{
@@ -30,8 +32,6 @@ trait ArrayCacheTrait
$this->cache[$key]['ttl'] = strtotime($this->ttl);
}
- $this->cleanupCache();
-
return $result;
}
@@ -47,8 +47,6 @@ trait ArrayCacheTrait
'value' => $value,
'ttl' => strtotime($this->ttl),
];
-
- $this->cleanupCache();
}
/**
@@ -61,12 +59,9 @@ trait ArrayCacheTrait
unset($this->cache[$key]);
}
- /**
- * Remove all keys from cache
- */
- protected function clearCache(): void
+ protected function startCacheCleanupLoop(): void
{
- $this->cache = [];
+ Loop::repeat(strtotime($this->ttlCheckInterval, 0) * 1000, fn() => $this->cleanupCache());
}
/**
@@ -75,11 +70,6 @@ trait ArrayCacheTrait
protected function cleanupCache(): void
{
$now = time();
- if ($this->nextTtlCheckTs > $now) {
- return;
- }
-
- $this->nextTtlCheckTs = strtotime($this->ttlCheckInterval, $now);
$oldKeys = [];
foreach ($this->cache as $cacheKey => $cacheValue) {
if ($cacheValue['ttl'] < $now) {
@@ -89,6 +79,14 @@ trait ArrayCacheTrait
foreach ($oldKeys as $oldKey) {
$this->unsetCache($oldKey);
}
+
+ Logger::log(
+ sprintf(
+ "cache for table:%s; keys left: %s; keys removed: %s",
+ $this->table, \count($this->cache), \count($oldKeys)
+ ),
+ Logger::VERBOSE
+ );
}
}
\ No newline at end of file
diff --git a/src/danog/MadelineProto/Db/MysqlArray.php b/src/danog/MadelineProto/Db/MysqlArray.php
index 8c842a7a..143e84b2 100644
--- a/src/danog/MadelineProto/Db/MysqlArray.php
+++ b/src/danog/MadelineProto/Db/MysqlArray.php
@@ -57,6 +57,8 @@ class MysqlArray implements DbArray
$instance->db = static::getDbConnection($settings);
$instance->ttl = $settings['cache_ttl'] ?? $instance->ttl;
+ $instance->startCacheCleanupLoop();
+
return call(static function() use($instance, $value) {
yield from static::renameTmpTable($instance, $value);
yield from $instance->prepareTable();
From 321787f718364b8f40165188e3019816cf0b8382 Mon Sep 17 00:00:00 2001
From: Alexander Pankratov
Date: Thu, 11 Jun 2020 00:23:35 +0300
Subject: [PATCH 29/29] Disable isset, optimize getInstance, concurrent
offsetSet
---
src/danog/MadelineProto/Db/DbArray.php | 13 ++++-
src/danog/MadelineProto/Db/MemoryArray.php | 9 +++-
src/danog/MadelineProto/Db/MysqlArray.php | 55 +++++++++++++---------
3 files changed, 53 insertions(+), 24 deletions(-)
diff --git a/src/danog/MadelineProto/Db/DbArray.php b/src/danog/MadelineProto/Db/DbArray.php
index b7d68586..b86385d8 100644
--- a/src/danog/MadelineProto/Db/DbArray.php
+++ b/src/danog/MadelineProto/Db/DbArray.php
@@ -8,10 +8,21 @@ use Amp\Promise;
interface DbArray extends DbType, \ArrayAccess, \Countable
{
public function getArrayCopy(): Promise;
- public function offsetExists($offset): Promise;
+ public function isset($key): Promise;
public function offsetGet($offset): Promise;
public function offsetSet($offset, $value);
public function offsetUnset($offset): Promise;
public function count(): Promise;
public function getIterator(): Producer;
+
+ /**
+ * @deprecated
+ * @internal
+ * @see DbArray::isset();
+ *
+ * @param mixed $offset
+ *
+ * @return bool
+ */
+ public function offsetExists($offset);
}
\ No newline at end of file
diff --git a/src/danog/MadelineProto/Db/MemoryArray.php b/src/danog/MadelineProto/Db/MemoryArray.php
index 24f319c3..8a97116f 100644
--- a/src/danog/MadelineProto/Db/MemoryArray.php
+++ b/src/danog/MadelineProto/Db/MemoryArray.php
@@ -28,9 +28,14 @@ class MemoryArray extends \ArrayIterator implements DbArray
});
}
- public function offsetExists($offset): Promise
+ public function offsetExists($offset)
{
- return call(fn() => parent::offsetExists($offset));
+ throw new \RuntimeException('Native isset not support promises. Use isset method');
+ }
+
+ public function isset($key): Promise
+ {
+ return call(fn() => parent::offsetExists($key));
}
public function offsetGet($offset): Promise
diff --git a/src/danog/MadelineProto/Db/MysqlArray.php b/src/danog/MadelineProto/Db/MysqlArray.php
index 143e84b2..e29e4657 100644
--- a/src/danog/MadelineProto/Db/MysqlArray.php
+++ b/src/danog/MadelineProto/Db/MysqlArray.php
@@ -2,14 +2,12 @@
namespace danog\MadelineProto\Db;
-use Amp\Loop;
use Amp\Mysql\Pool;
use Amp\Producer;
use Amp\Promise;
use Amp\Sql\ResultSet;
use danog\MadelineProto\Logger;
use function Amp\call;
-use function Amp\Promise\wait;
class MysqlArray implements DbArray
{
@@ -50,9 +48,14 @@ class MysqlArray implements DbArray
*/
public static function getInstance(string $name, $value = null, string $tablePrefix = '', array $settings = []): Promise
{
- $instance = new static();
+ $tableName = "{$tablePrefix}_{$name}";
+ if ($value instanceof self && $value->table === $tableName) {
+ $instance = &$value;
+ } else {
+ $instance = new static();
+ $instance->table = $tableName;
+ }
- $instance->table = "{$tablePrefix}_{$name}";
$instance->settings = $settings;
$instance->db = static::getDbConnection($settings);
$instance->ttl = $settings['cache_ttl'] ?? $instance->ttl;
@@ -60,9 +63,13 @@ class MysqlArray implements DbArray
$instance->startCacheCleanupLoop();
return call(static function() use($instance, $value) {
- yield from static::renameTmpTable($instance, $value);
yield from $instance->prepareTable();
- yield from static::migrateDataToDb($instance, $value);
+
+ //Skip migrations if its same object
+ if ($instance !== $value) {
+ yield from static::renameTmpTable($instance, $value);
+ yield from static::migrateDataToDb($instance, $value);
+ }
return $instance;
});
@@ -109,7 +116,7 @@ class MysqlArray implements DbArray
$total = count($value);
foreach ($value as $key => $item) {
$counter++;
- if ($counter % 100 === 0) {
+ if ($counter % 500 === 0) {
yield $instance->offsetSet($key, $item);
Logger::log("Loading data to table {$instance->table}: $counter/$total", Logger::WARNING);
} else {
@@ -121,21 +128,21 @@ class MysqlArray implements DbArray
}
}
+ public function offsetExists($index): bool
+ {
+ throw new \RuntimeException('Native isset not support promises. Use isset method');
+ }
+
/**
- * Check if offset exists
+ * Check if key isset
*
- * @link https://php.net/manual/en/arrayiterator.offsetexists.php
- *
- * @param string $index
- * The offset being checked.
- *
+ * @param $key
*
* @return Promise true if the offset exists, otherwise false
- * @throws \Throwable
*/
- public function offsetExists($index): Promise
+ public function isset($key): Promise
{
- return call(fn() => yield $this->offsetGet($index) !== null);
+ return call(fn() => yield $this->offsetGet($key) !== null);
}
@@ -177,18 +184,24 @@ class MysqlArray implements DbArray
if ($this->getCache($index) === $value) {
return call(fn()=>null);
}
+
$this->setCache($index, $value);
- return $this->request("
- INSERT INTO `{$this->table}`
- SET `key` = :index, `value` = :value
- ON DUPLICATE KEY UPDATE `value` = :value
- ",
+ $request = $this->request("
+ INSERT INTO `{$this->table}`
+ SET `key` = :index, `value` = :value
+ ON DUPLICATE KEY UPDATE `value` = :value
+ ",
[
'index' => $index,
'value' => serialize($value),
]
);
+
+ //Ensure that cache is synced with latest insert in case of concurrent requests.
+ $request->onResolve(fn() => $this->setCache($index, $value));
+
+ return $request;
}
/**