xref: /template/strap/ComboStrap/Event.php (revision 4d15adcc14acacfdcef0f5f356b5ad038e9e1da9)
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
219fc13f00Sgerardnico    const CANONICAL = "event";
22b0217656Sgerardnico
23b0217656Sgerardnico    /**
24b0217656Sgerardnico     * Uppercase mandatory (the column is uppercased when returnd from a *)
25b0217656Sgerardnico     */
26b0217656Sgerardnico    const EVENT_NAME_ATTRIBUTE = "NAME";
27b0217656Sgerardnico
28b0217656Sgerardnico    /**
29b0217656Sgerardnico     * Uppercase mandatory (the column is uppercased when returnd from a *)
30b0217656Sgerardnico     */
31b0217656Sgerardnico    const EVENT_DATA_ATTRIBUTE = "DATA";
32b0217656Sgerardnico    /**
33b0217656Sgerardnico     * Uppercase mandatory (the column is uppercased when returnd from a *)
34b0217656Sgerardnico     */
35b0217656Sgerardnico    const TIMESTAMP_ATTRIBUTE = "TIMESTAMP";
36c3437056SNickeau
37c3437056SNickeau    /**
38c3437056SNickeau     * process all replication request, created with {@link Event::createEvent()}
39c3437056SNickeau     *
40c3437056SNickeau     * by default, there is 5 pages in a default dokuwiki installation in the wiki namespace)
41b0217656Sgerardnico     *
42b0217656Sgerardnico     * @param int $maxEvent In case of a start or if there is a recursive bug. We don't want to take all the resources
43b0217656Sgerardnico     *
44c3437056SNickeau     */
45b0217656Sgerardnico    public static function dispatchEvent(int $maxEvent = 10)
46c3437056SNickeau    {
47*4d15adccSNico        $comboFunctionName = 'ComboDispatchEvent';
48*4d15adccSNico        print "$comboFunctionName(): Trying to get a lock" . NL;
490360a848Sgerardnico        $lock = self::getLock();
50a6d63b89Sgerardnico        try {
51a6d63b89Sgerardnico            $lock->acquire();
52a6d63b89Sgerardnico        } catch (ExceptionTimeOut $e) {
53a6d63b89Sgerardnico            // process running
54a6d63b89Sgerardnico            return;
55a6d63b89Sgerardnico        }
56*4d15adccSNico        print "$comboFunctionName(): Locked" . NL;
57a6d63b89Sgerardnico
58a6d63b89Sgerardnico        try {
5904fd306cSNickeau            try {
60c3437056SNickeau                $sqlite = Sqlite::createOrGetBackendSqlite();
6104fd306cSNickeau            } catch (ExceptionSqliteNotAvailable $e) {
6204fd306cSNickeau                LogUtility::error("Sqlite is mandatory for asynchronous event", self::CANONICAL, $e);
63c3437056SNickeau                return;
64c3437056SNickeau            }
65c3437056SNickeau
6670bbd7f1Sgerardnico
67c3437056SNickeau            $rows = [];
68b0217656Sgerardnico            /**
69b0217656Sgerardnico             * Returning clause
70b0217656Sgerardnico             * does not work
71b0217656Sgerardnico             */
72b0217656Sgerardnico            $version = $sqlite->getVersion();
73b0217656Sgerardnico            if ($version > "3.35.0") {
74b0217656Sgerardnico
75b0217656Sgerardnico                // returning clause is available since 3.35 on delete
76b0217656Sgerardnico                // https://www.sqlite.org/lang_returning.html
77b0217656Sgerardnico
78b0217656Sgerardnico                $eventTableName = self::EVENT_TABLE_NAME;
79b0217656Sgerardnico                $statement = "delete from {$eventTableName} returning *";
80b0217656Sgerardnico                // https://www.sqlite.org/lang_delete.html#optional_limit_and_order_by_clauses
81b0217656Sgerardnico                if ($sqlite->hasOption("SQLITE_ENABLE_UPDATE_DELETE_LIMIT")) {
82b0217656Sgerardnico                    $statement .= "order by timestamp limit {$maxEvent}";
83b0217656Sgerardnico                }
84b0217656Sgerardnico                $request = $sqlite->createRequest()
85b0217656Sgerardnico                    ->setStatement($statement);
86b0217656Sgerardnico                try {
87b0217656Sgerardnico                    $rows = $request->execute()
88b0217656Sgerardnico                        ->getRows();
89b0217656Sgerardnico                    if (sizeof($rows) === 0) {
90b0217656Sgerardnico                        return;
91b0217656Sgerardnico                    }
92b0217656Sgerardnico                } catch (ExceptionCompile $e) {
93b0217656Sgerardnico                    LogUtility::error($e->getMessage(), $e->getCanonical(), $e);
94b0217656Sgerardnico                } finally {
95b0217656Sgerardnico                    $request->close();
96b0217656Sgerardnico                }
97b0217656Sgerardnico
98b0217656Sgerardnico            }
99c3437056SNickeau
100c3437056SNickeau            /**
101c3437056SNickeau             * Error in the block before or not the good version
102c3437056SNickeau             * We try to get the records with a select/delete
103c3437056SNickeau             */
104c3437056SNickeau            if (sizeof($rows) === 0) {
105c3437056SNickeau
106c3437056SNickeau
107c3437056SNickeau                // technically the lock system of dokuwiki does not allow two process to run on
108c3437056SNickeau                // the indexer, we trust it
109c3437056SNickeau                $attributes = [self::EVENT_NAME_ATTRIBUTE, self::EVENT_DATA_ATTRIBUTE, DatabasePageRow::ROWID];
110c3437056SNickeau                $select = Sqlite::createSelectFromTableAndColumns(self::EVENT_TABLE_NAME, $attributes);
111b0217656Sgerardnico                $select .= " order by " . self::TIMESTAMP_ATTRIBUTE . " limit {$maxEvent}";
112c3437056SNickeau                $request = $sqlite->createRequest()
113c3437056SNickeau                    ->setQuery($select);
114c3437056SNickeau
115c3437056SNickeau                $rowsSelected = [];
116c3437056SNickeau                try {
117c3437056SNickeau                    $rowsSelected = $request->execute()
118c3437056SNickeau                        ->getRows();
119c3437056SNickeau                    if (sizeof($rowsSelected) === 0) {
120c3437056SNickeau                        return;
121c3437056SNickeau                    }
12204fd306cSNickeau                } catch (ExceptionCompile $e) {
123c3437056SNickeau                    LogUtility::msg("Error while retrieving the event {$e->getMessage()}", LogUtility::LVL_MSG_ERROR, $e->getCanonical());
124c3437056SNickeau                    return;
125c3437056SNickeau                } finally {
126c3437056SNickeau                    $request->close();
127c3437056SNickeau                }
128c3437056SNickeau
129c3437056SNickeau                $eventTableName = self::EVENT_TABLE_NAME;
130c3437056SNickeau                $rows = [];
131c3437056SNickeau                foreach ($rowsSelected as $row) {
132c3437056SNickeau                    $request = $sqlite->createRequest()
133c3437056SNickeau                        ->setQueryParametrized("delete from $eventTableName where rowid = ? ", [$row[DatabasePageRow::ROWID]]);
134c3437056SNickeau                    try {
135b0217656Sgerardnico                        $changeCount = $request->execute()->getChangeCount();
136c3437056SNickeau                        if ($changeCount !== 1) {
137c3437056SNickeau                            LogUtility::msg("The delete of the event was not successful or it was deleted by another process", LogUtility::LVL_MSG_ERROR);
138c3437056SNickeau                        } else {
139c3437056SNickeau                            $rows[] = $row;
140c3437056SNickeau                        }
14104fd306cSNickeau                    } catch (ExceptionCompile $e) {
142c3437056SNickeau                        LogUtility::msg("Error while deleting the event. Message {$e->getMessage()}", LogUtility::LVL_MSG_ERROR, $e->getCanonical());
143c3437056SNickeau                        return;
144c3437056SNickeau                    } finally {
145c3437056SNickeau                        $request->close();
146c3437056SNickeau                    }
147c3437056SNickeau                }
148c3437056SNickeau
149c3437056SNickeau
150c3437056SNickeau            }
151c3437056SNickeau
152c3437056SNickeau
153c3437056SNickeau            $eventCounter = 0;
154c3437056SNickeau            foreach ($rows as $row) {
155c3437056SNickeau                $eventCounter++;
156c3437056SNickeau                $eventName = $row[self::EVENT_NAME_ATTRIBUTE];
157c3437056SNickeau                $eventData = [];
158c3437056SNickeau                $eventDataJson = $row[self::EVENT_DATA_ATTRIBUTE];
159c3437056SNickeau                if ($eventDataJson !== null) {
160c3437056SNickeau                    try {
161c3437056SNickeau                        $eventData = Json::createFromString($eventDataJson)->toArray();
16204fd306cSNickeau                    } catch (ExceptionCompile $e) {
163c3437056SNickeau                        LogUtility::msg("The stored data for the event $eventName was not in the json format");
164c3437056SNickeau                        continue;
165c3437056SNickeau                    }
166c3437056SNickeau                }
167c3437056SNickeau                \dokuwiki\Extension\Event::createAndTrigger($eventName, $eventData);
168c3437056SNickeau
169c3437056SNickeau                if ($eventCounter >= $maxEvent) {
170c3437056SNickeau                    break;
171c3437056SNickeau                }
172c3437056SNickeau
173c3437056SNickeau            }
1749fc13f00Sgerardnico        } catch (\Exception $e) {
1759fc13f00Sgerardnico            LogUtility::internalError("An internal error has runned on event. " . $e->getMessage(), self::CANONICAL, $e);
176a6d63b89Sgerardnico        } finally {
177a6d63b89Sgerardnico            $lock->release();
178*4d15adccSNico            print "$comboFunctionName(): Lock Released" . NL;
179a6d63b89Sgerardnico        }
180c3437056SNickeau
181c3437056SNickeau    }
182c3437056SNickeau
183c3437056SNickeau    /**
184c3437056SNickeau     * Ask a replication in the background
185c3437056SNickeau     * @param string $name - a string with the reason
186c3437056SNickeau     * @param array $data
187c3437056SNickeau     */
188c3437056SNickeau    public static
189c3437056SNickeau    function createEvent(string $name, array $data)
190c3437056SNickeau    {
191c3437056SNickeau
19204fd306cSNickeau        try {
193c3437056SNickeau            $sqlite = Sqlite::createOrGetBackendSqlite();
19404fd306cSNickeau        } catch (ExceptionSqliteNotAvailable $e) {
19504fd306cSNickeau            LogUtility::error("Unable to create the event $name. Sqlite is not available");
196c3437056SNickeau            return;
197c3437056SNickeau        }
198c3437056SNickeau
199c3437056SNickeau        /**
200c3437056SNickeau         * If not present
201c3437056SNickeau         */
202c3437056SNickeau        $entry = array(
203c3437056SNickeau            "name" => $name,
204c3437056SNickeau            "timestamp" => Iso8601Date::createFromNow()->toString()
205c3437056SNickeau        );
206c3437056SNickeau
20704fd306cSNickeau
208c3437056SNickeau        $entry["data"] = Json::createFromArray($data)->toPrettyJsonString();
20904fd306cSNickeau        $entry["data_hash"] = md5($entry["data"]);
210c3437056SNickeau
211c3437056SNickeau        /**
212c3437056SNickeau         * Execute
213c3437056SNickeau         */
214c3437056SNickeau        $request = $sqlite->createRequest()
215c3437056SNickeau            ->setTableRow(self::EVENT_TABLE_NAME, $entry);
216c3437056SNickeau        try {
217c3437056SNickeau            $request->execute();
21804fd306cSNickeau        } catch (ExceptionCompile $e) {
21904fd306cSNickeau            LogUtility::error("Unable to create the event $name. Error:" . $e->getMessage(), self::CANONICAL, $e);
220c3437056SNickeau        } finally {
221c3437056SNickeau            $request->close();
222c3437056SNickeau        }
223c3437056SNickeau
224c3437056SNickeau
225c3437056SNickeau    }
226c3437056SNickeau
227c3437056SNickeau    /**
228c3437056SNickeau     * @param $pageId
229c3437056SNickeau     *
230c3437056SNickeau     * This is equivalent to {@link TaskRunner}
231c3437056SNickeau     *
232c3437056SNickeau     * lib/exe/taskrunner.php?id='.rawurlencode($ID)
233c3437056SNickeau     * $taskRunner = new \dokuwiki\TaskRunner();
234c3437056SNickeau     * $taskRunner->run();
235c3437056SNickeau     *
236c3437056SNickeau     */
237c3437056SNickeau    public static function startTaskRunnerForPage($pageId)
238c3437056SNickeau    {
239c3437056SNickeau        $tmp = []; // No event data
240c3437056SNickeau        $tmp['page'] = $pageId;
241c3437056SNickeau        $evt = new \dokuwiki\Extension\Event('INDEXER_TASKS_RUN', $tmp);
24204fd306cSNickeau        $evt->advise_before();
243c3437056SNickeau        $evt->advise_after();
244c3437056SNickeau    }
245c3437056SNickeau
2464cadd4f8SNickeau
24704fd306cSNickeau    public static function getQueue(string $eventName = null): array
24804fd306cSNickeau    {
24904fd306cSNickeau        try {
25004fd306cSNickeau            $sqlite = Sqlite::createOrGetBackendSqlite();
25104fd306cSNickeau        } catch (ExceptionSqliteNotAvailable $e) {
25204fd306cSNickeau            LogUtility::internalError("Sqlite is not available, no events was returned", self::CANONICAL);
25304fd306cSNickeau            return [];
25404fd306cSNickeau        }
2554cadd4f8SNickeau
2564cadd4f8SNickeau        /**
2574cadd4f8SNickeau         * Execute
2584cadd4f8SNickeau         */
2594cadd4f8SNickeau        $attributes = [self::EVENT_NAME_ATTRIBUTE, self::EVENT_DATA_ATTRIBUTE, DatabasePageRow::ROWID];
2604cadd4f8SNickeau        $select = Sqlite::createSelectFromTableAndColumns(self::EVENT_TABLE_NAME, $attributes);
26104fd306cSNickeau        $request = $sqlite->createRequest();
26204fd306cSNickeau        if (empty($eventName)) {
26304fd306cSNickeau            $request->setQuery($select);
26404fd306cSNickeau        } else {
26504fd306cSNickeau            $request->setQueryParametrized($select . " where " . self::EVENT_NAME_ATTRIBUTE . " = ?", [$eventName]);
26604fd306cSNickeau        }
2674cadd4f8SNickeau        try {
2684cadd4f8SNickeau            return $request->execute()
2694cadd4f8SNickeau                ->getRows();
27004fd306cSNickeau        } catch (ExceptionCompile $e) {
27104fd306cSNickeau            LogUtility::internalError("Unable to get the queue. Error:" . $e->getMessage(), self::CANONICAL, $e);
27204fd306cSNickeau            return [];
2734cadd4f8SNickeau        } finally {
2744cadd4f8SNickeau            $request->close();
2754cadd4f8SNickeau        }
2764cadd4f8SNickeau
2774cadd4f8SNickeau    }
2784cadd4f8SNickeau
2794cadd4f8SNickeau    /**
28004fd306cSNickeau     * @throws ExceptionCompile
2814cadd4f8SNickeau     */
2824cadd4f8SNickeau    public static function purgeQueue(): int
2834cadd4f8SNickeau    {
2844cadd4f8SNickeau        $sqlite = Sqlite::createOrGetBackendSqlite();
2854cadd4f8SNickeau        if ($sqlite === null) {
28604fd306cSNickeau            throw new ExceptionCompile("Sqlite is not available");
287c3437056SNickeau        }
288c3437056SNickeau
289c3437056SNickeau
290c3437056SNickeau        /**
291c3437056SNickeau         * Execute
292c3437056SNickeau         */
29304fd306cSNickeau        /** @noinspection SqlWithoutWhere */
294c3437056SNickeau        $request = $sqlite->createRequest()
2954cadd4f8SNickeau            ->setQuery("delete from " . self::EVENT_TABLE_NAME);
296c3437056SNickeau        try {
2974cadd4f8SNickeau            return $request->execute()
2984cadd4f8SNickeau                ->getChangeCount();
29904fd306cSNickeau        } catch (ExceptionCompile $e) {
30004fd306cSNickeau            throw new ExceptionCompile("Unable to count the number of event in the queue. Error:" . $e->getMessage(), self::CANONICAL, 0, $e);
301c3437056SNickeau        } finally {
302c3437056SNickeau            $request->close();
303c3437056SNickeau        }
304c3437056SNickeau    }
305c3437056SNickeau
30604fd306cSNickeau    /**
30704fd306cSNickeau     * @throws ExceptionCompile
30804fd306cSNickeau     */
30904fd306cSNickeau    public static function getEvents(string $eventName): array
31004fd306cSNickeau    {
31104fd306cSNickeau        return Event::getQueue($eventName);
31204fd306cSNickeau    }
31304fd306cSNickeau
3140360a848Sgerardnico    public static function getLock(): Lock
3150360a848Sgerardnico    {
3160360a848Sgerardnico        return Lock::create("combo-event");
3170360a848Sgerardnico    }
3180360a848Sgerardnico
319c3437056SNickeau
320c3437056SNickeau}
321