xref: /plugin/combo/ComboStrap/Event.php (revision 70bbd7f1f72440223cc13f3495efdcb2b0a11514)
1<?php
2
3
4namespace ComboStrap;
5
6/**
7 * Class Event
8 * @package ComboStrap
9 * Asynchronous pub/sub system
10 *
11 * Dokuwiki allows event but they are synchronous
12 * because php does not live in multiple thread
13 *
14 * With the help of Sqlite, we make them asynchronous
15 */
16class Event
17{
18
19    const EVENT_TABLE_NAME = "EVENTS_QUEUE";
20
21    const CANONICAL = "support";
22    const EVENT_NAME_ATTRIBUTE = "name";
23    const EVENT_DATA_ATTRIBUTE = "data";
24
25    /**
26     * process all replication request, created with {@link Event::createEvent()}
27     *
28     * by default, there is 5 pages in a default dokuwiki installation in the wiki namespace)
29     */
30    public static function dispatchEvent($maxEvent = 10)
31    {
32
33        try {
34            $sqlite = Sqlite::createOrGetBackendSqlite();
35        } catch (ExceptionSqliteNotAvailable $e) {
36            LogUtility::error("Sqlite is mandatory for asynchronous event", self::CANONICAL, $e);
37            return;
38        }
39
40        /**
41         * In case of a start or if there is a recursive bug
42         * We don't want to take all the resources
43         */
44        $maxBackgroundEventLow = 10;
45
46
47        $rows = [];
48//        /**
49//         * Returning clause
50//         * does not work
51//         */
52//        $version = $sqlite->getVersion();
53//        $isCi = PluginUtility::isCi(); // Sqlite plugin seems to not support the returning clause - it returns a number as result set in CI
54//        $isDev = PluginUtility::isDevOrTest(); // may not work for normal users
55//        if ($version > "3.35.0" && $isDev && !$isCi) {
56//
57//            // returning clause is available since 3.35 on delete
58//            // https://www.sqlite.org/lang_returning.html
59//
60//            $eventTableName = self::EVENT_TABLE_NAME;
61//            $statement = "delete from {$eventTableName} returning *";
62//            // https://www.sqlite.org/lang_delete.html#optional_limit_and_order_by_clauses
63//            if ($sqlite->hasOption("SQLITE_ENABLE_UPDATE_DELETE_LIMIT")) {
64//                $statement .= "order by timestamp limit {$maxBackgroundEventLow}";
65//            }
66//            $request = $sqlite->createRequest()
67//                ->setStatement($statement);
68//            try {
69//                $rows = $request->execute()
70//                    ->getRows();
71//                if (sizeof($rows) === 0) {
72//                    return;
73//                }
74//            } catch (ExceptionCompile $e) {
75//                LogUtility::error($e->getMessage(), $e->getCanonical(), $e);
76//            } finally {
77//                $request->close();
78//            }
79//
80//        }
81
82        /**
83         * Error in the block before or not the good version
84         * We try to get the records with a select/delete
85         */
86        if (sizeof($rows) === 0) {
87
88
89            // technically the lock system of dokuwiki does not allow two process to run on
90            // the indexer, we trust it
91            $attributes = [self::EVENT_NAME_ATTRIBUTE, self::EVENT_DATA_ATTRIBUTE, DatabasePageRow::ROWID];
92            $select = Sqlite::createSelectFromTableAndColumns(self::EVENT_TABLE_NAME, $attributes);
93            $request = $sqlite->createRequest()
94                ->setQuery($select);
95
96            $rowsSelected = [];
97            try {
98                $rowsSelected = $request->execute()
99                    ->getRows();
100                if (sizeof($rowsSelected) === 0) {
101                    return;
102                }
103            } catch (ExceptionCompile $e) {
104                LogUtility::msg("Error while retrieving the event {$e->getMessage()}", LogUtility::LVL_MSG_ERROR, $e->getCanonical());
105                return;
106            } finally {
107                $request->close();
108            }
109
110            $eventTableName = self::EVENT_TABLE_NAME;
111            $rows = [];
112            foreach ($rowsSelected as $row) {
113                $request = $sqlite->createRequest()
114                    ->setQueryParametrized("delete from $eventTableName where rowid = ? ", [$row[DatabasePageRow::ROWID]]);
115                try {
116                    $changeCount = $request->execute()
117                        ->getChangeCount();
118                    if ($changeCount !== 1) {
119                        LogUtility::msg("The delete of the event was not successful or it was deleted by another process", LogUtility::LVL_MSG_ERROR);
120                    } else {
121                        $rows[] = $row;
122                    }
123                } catch (ExceptionCompile $e) {
124                    LogUtility::msg("Error while deleting the event. Message {$e->getMessage()}", LogUtility::LVL_MSG_ERROR, $e->getCanonical());
125                    return;
126                } finally {
127                    $request->close();
128                }
129            }
130
131
132        }
133
134
135        $eventCounter = 0;
136        foreach ($rows as $row) {
137            $eventCounter++;
138            $eventName = $row[self::EVENT_NAME_ATTRIBUTE];
139            $eventData = [];
140            $eventDataJson = $row[self::EVENT_DATA_ATTRIBUTE];
141            if ($eventDataJson !== null) {
142                try {
143                    $eventData = Json::createFromString($eventDataJson)->toArray();
144                } catch (ExceptionCompile $e) {
145                    LogUtility::msg("The stored data for the event $eventName was not in the json format");
146                    continue;
147                }
148            }
149            \dokuwiki\Extension\Event::createAndTrigger($eventName, $eventData);
150
151            if ($eventCounter >= $maxEvent) {
152                break;
153            }
154
155        }
156
157    }
158
159    /**
160     * Ask a replication in the background
161     * @param string $name - a string with the reason
162     * @param array $data
163     */
164    public static
165    function createEvent(string $name, array $data)
166    {
167
168        try {
169            $sqlite = Sqlite::createOrGetBackendSqlite();
170        } catch (ExceptionSqliteNotAvailable $e) {
171            LogUtility::error("Unable to create the event $name. Sqlite is not available");
172            return;
173        }
174
175        /**
176         * If not present
177         */
178        $entry = array(
179            "name" => $name,
180            "timestamp" => Iso8601Date::createFromNow()->toString()
181        );
182
183
184        $entry["data"] = Json::createFromArray($data)->toPrettyJsonString();
185        $entry["data_hash"] = md5($entry["data"]);
186
187        /**
188         * Execute
189         */
190        $request = $sqlite->createRequest()
191            ->setTableRow(self::EVENT_TABLE_NAME, $entry);
192        try {
193            $request->execute();
194        } catch (ExceptionCompile $e) {
195            LogUtility::error("Unable to create the event $name. Error:" . $e->getMessage(), self::CANONICAL, $e);
196        } finally {
197            $request->close();
198        }
199
200
201    }
202
203    /**
204     * @param $pageId
205     *
206     * This is equivalent to {@link TaskRunner}
207     *
208     * lib/exe/taskrunner.php?id='.rawurlencode($ID)
209     * $taskRunner = new \dokuwiki\TaskRunner();
210     * $taskRunner->run();
211     *
212     */
213    public static function startTaskRunnerForPage($pageId)
214    {
215        $tmp = []; // No event data
216        $tmp['page'] = $pageId;
217        $evt = new \dokuwiki\Extension\Event('INDEXER_TASKS_RUN', $tmp);
218        $evt->advise_before();
219        $evt->advise_after();
220    }
221
222
223    public static function getQueue(string $eventName = null): array
224    {
225        try {
226            $sqlite = Sqlite::createOrGetBackendSqlite();
227        } catch (ExceptionSqliteNotAvailable $e) {
228            LogUtility::internalError("Sqlite is not available, no events was returned", self::CANONICAL);
229            return [];
230        }
231
232        /**
233         * Execute
234         */
235        $attributes = [self::EVENT_NAME_ATTRIBUTE, self::EVENT_DATA_ATTRIBUTE, DatabasePageRow::ROWID];
236        $select = Sqlite::createSelectFromTableAndColumns(self::EVENT_TABLE_NAME, $attributes);
237        $request = $sqlite->createRequest();
238        if (empty($eventName)) {
239            $request->setQuery($select);
240        } else {
241            $request->setQueryParametrized($select . " where " . self::EVENT_NAME_ATTRIBUTE . " = ?", [$eventName]);
242        }
243        try {
244            return $request->execute()
245                ->getRows();
246        } catch (ExceptionCompile $e) {
247            LogUtility::internalError("Unable to get the queue. Error:" . $e->getMessage(), self::CANONICAL, $e);
248            return [];
249        } finally {
250            $request->close();
251        }
252
253    }
254
255    /**
256     * @throws ExceptionCompile
257     */
258    public static function purgeQueue(): int
259    {
260        $sqlite = Sqlite::createOrGetBackendSqlite();
261        if ($sqlite === null) {
262            throw new ExceptionCompile("Sqlite is not available");
263        }
264
265
266        /**
267         * Execute
268         */
269        /** @noinspection SqlWithoutWhere */
270        $request = $sqlite->createRequest()
271            ->setQuery("delete from " . self::EVENT_TABLE_NAME);
272        try {
273            return $request->execute()
274                ->getChangeCount();
275        } catch (ExceptionCompile $e) {
276            throw new ExceptionCompile("Unable to count the number of event in the queue. Error:" . $e->getMessage(), self::CANONICAL, 0, $e);
277        } finally {
278            $request->close();
279        }
280    }
281
282    /**
283     * @throws ExceptionCompile
284     */
285    public static function getEvents(string $eventName): array
286    {
287        return Event::getQueue($eventName);
288    }
289
290
291}
292