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