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