From d2e3dee23b721eed70449530628aba13da3c3fe0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Gieling?= Date: Sat, 30 Jan 2021 22:59:40 +0100 Subject: Watch events (#1367) Watch current poll for updates an apply immediately --- lib/Controller/ResponseHandle.php | 13 ++++ lib/Controller/WatchController.php | 67 +++++++++++++++++++++ lib/Db/Watch.php | 63 ++++++++++++++++++++ lib/Db/WatchMapper.php | 75 +++++++++++++++++++++++ lib/Exceptions/NoUpdatesException.php | 32 ++++++++++ lib/Migration/Version0108Date20210127135802.php | 79 +++++++++++++++++++++++++ lib/Service/CommentService.php | 26 +++++--- lib/Service/OptionService.php | 38 ++++++++---- lib/Service/PollService.php | 15 ++++- lib/Service/VoteService.php | 53 ++++++++++------- lib/Service/WatchService.php | 72 ++++++++++++++++++++++ 11 files changed, 489 insertions(+), 44 deletions(-) create mode 100644 lib/Controller/WatchController.php create mode 100644 lib/Db/Watch.php create mode 100644 lib/Db/WatchMapper.php create mode 100644 lib/Exceptions/NoUpdatesException.php create mode 100644 lib/Migration/Version0108Date20210127135802.php create mode 100644 lib/Service/WatchService.php (limited to 'lib') diff --git a/lib/Controller/ResponseHandle.php b/lib/Controller/ResponseHandle.php index 39b4e91d..350ff267 100644 --- a/lib/Controller/ResponseHandle.php +++ b/lib/Controller/ResponseHandle.php @@ -28,6 +28,7 @@ use OCP\AppFramework\Http; use OCP\AppFramework\Http\DataResponse; use OCP\AppFramework\Db\DoesNotExistException; +use OCA\Polls\Exceptions\NoUpdatesException; use OCA\Polls\Exceptions\Exception; trait ResponseHandle { @@ -44,6 +45,18 @@ trait ResponseHandle { } } + /** + * response + * @NoAdminRequired + */ + protected function responseLong(Closure $callback): DataResponse { + try { + return new DataResponse($callback(), Http::STATUS_OK); + } catch (NoUpdatesException $e) { + return new DataResponse([], Http::STATUS_NOT_MODIFIED); + } + } + /** * responseCreate * @NoAdminRequired diff --git a/lib/Controller/WatchController.php b/lib/Controller/WatchController.php new file mode 100644 index 00000000..a035bcf4 --- /dev/null +++ b/lib/Controller/WatchController.php @@ -0,0 +1,67 @@ + + * + * @author René Gieling + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\Polls\Controller; + +use OCP\IRequest; +use OCP\AppFramework\Controller; +use OCP\AppFramework\Http\DataResponse; +use OCA\Polls\Exceptions\NoUpdatesException; +use OCA\Polls\Service\WatchService; + +class WatchController extends Controller { + + /** @var WatchService */ + private $watchService; + + use ResponseHandle; + + public function __construct( + string $appName, + IRequest $request, + WatchService $watchService + ) { + parent::__construct($appName, $request); + $this->watchService = $watchService; + } + + /** + * Watch poll for updates + * @NoAdminRequired + * @NoCSRFRequired + */ + public function watchSinglePoll(int $pollId, ?int $offset = 0): DataResponse { + return $this->responseLong(function () use ($pollId, $offset) { + $start = time(); + $timeout = 30; + while (empty($updates) && time() <= $start + $timeout) { + sleep(1); + $updates = $this->watchService->getUpdates($pollId, $offset ? $offset : $start); + } + if (empty($updates)) { + throw new NoUpdatesException; + } + return ['updates' => $updates]; + }); + } +} diff --git a/lib/Db/Watch.php b/lib/Db/Watch.php new file mode 100644 index 00000000..ea6a518c --- /dev/null +++ b/lib/Db/Watch.php @@ -0,0 +1,63 @@ + + * + * @author René Gieling + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\Polls\Db; + +use JsonSerializable; + +use OCP\AppFramework\Db\Entity; + +/** + * @method int getId() + * @method void setId(integer $value) + * @method int getPollId() + * @method void setPollId(integer $value) + * @method string getTable() + * @method void setTable(string $value) + * @method string getUpdated() + * @method void setUpdated(string $value) + */ +class Watch extends Entity implements JsonSerializable { + public const OBJECT_POLLS = "polls"; + public const OBJECT_VOTES = "votes"; + public const OBJECT_OPTIONS = "options"; + public const OBJECT_COMMENTS = "comments"; + + /** @var int $pollId */ + protected $pollId; + + /** @var string $tableId */ + protected $table; + + /** @var string $updated */ + protected $updated; + + public function jsonSerialize() { + return [ + 'id' => intval($this->id), + 'pollId' => intval($this->pollId), + 'table' => $this->table, + 'updated' => $this->updated, + ]; + } +} diff --git a/lib/Db/WatchMapper.php b/lib/Db/WatchMapper.php new file mode 100644 index 00000000..38bf18b9 --- /dev/null +++ b/lib/Db/WatchMapper.php @@ -0,0 +1,75 @@ + + * + * @author Vinzenz Rosenkranz + * @author René Gieling + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\Polls\Db; + +use OCP\IDBConnection; +use OCP\AppFramework\Db\QBMapper; + +/** + * @template-extends QBMapper + */ +class WatchMapper extends QBMapper { + public function __construct(IDBConnection $db) { + parent::__construct($db, 'polls_watch', '\OCA\Polls\Db\Watch'); + } + + /** + * @throws \OCP\AppFramework\Db\DoesNotExistException if not found + * @return Watch[] + */ + public function findUpdatesForPollId(int $pollId, int $offset): array { + $qb = $this->db->getQueryBuilder(); + + $qb->select('*') + ->from($this->getTableName()) + ->where( + $qb->expr()->eq('poll_id', $qb->createNamedParameter($pollId)) + ) + ->andWhere( + $qb->expr()->gt('updated', $qb->createNamedParameter($offset)) + ); + + return $this->findEntities($qb); + } + + /** + * @throws \OCP\AppFramework\Db\DoesNotExistException if not found + * @return Watch + */ + public function findForPollIdAndTable(int $pollId, string $table): Watch { + $qb = $this->db->getQueryBuilder(); + + $qb->select('*') + ->from($this->getTableName()) + ->where( + $qb->expr()->eq('poll_id', $qb->createNamedParameter($pollId)) + ) + ->andWhere( + $qb->expr()->eq('table', $qb->createNamedParameter($table)) + ); + + return $this->findEntity($qb); + } +} diff --git a/lib/Exceptions/NoUpdatesException.php b/lib/Exceptions/NoUpdatesException.php new file mode 100644 index 00000000..a0f836c6 --- /dev/null +++ b/lib/Exceptions/NoUpdatesException.php @@ -0,0 +1,32 @@ + + * + * @author René Gieling + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\Polls\Exceptions; + +use OCP\AppFramework\Http; + +class NoUpdatesException extends Exception { + public function __construct($e = 'No updates') { + parent::__construct($e, Http::STATUS_NOT_MODIFIED); + } +} diff --git a/lib/Migration/Version0108Date20210127135802.php b/lib/Migration/Version0108Date20210127135802.php new file mode 100644 index 00000000..1351abed --- /dev/null +++ b/lib/Migration/Version0108Date20210127135802.php @@ -0,0 +1,79 @@ + + * + * @author René Gieling + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\Polls\Migration; + +use OCP\DB\ISchemaWrapper; +use OCP\IConfig; +use OCP\IDBConnection; +use OCP\Migration\SimpleMigrationStep; +use OCP\Migration\IOutput; + +class Version0108Date20210127135802 extends SimpleMigrationStep { + + /** @var IDBConnection */ + protected $connection; + + /** @var IConfig */ + protected $config; + + public function __construct(IDBConnection $connection, IConfig $config) { + $this->connection = $connection; + $this->config = $config; + } + + public function changeSchema(IOutput $output, \Closure $schemaClosure, array $options) { + /** @var ISchemaWrapper $schema */ + $schema = $schemaClosure(); + if (!$schema->hasTable('polls_watch')) { + $table = $schema->createTable('polls_watch'); + $table->addColumn('id', 'integer', [ + 'autoincrement' => true, + 'notnull' => true, + ]); + $table->addColumn('table', 'string', [ + 'length' => 64, + 'notnull' => true, + 'default' => '' + ]); + $table->addColumn('poll_id', 'integer', [ + 'length' => 11, + 'notnull' => true, + 'default' => 0 + ]); + $table->addColumn('updated', 'integer', [ + 'length' => 11, + 'notnull' => true, + 'default' => 0 + ]); + $table->setPrimaryKey(['id']); + } else { + $table = $schema->getTable('polls_watch'); + } + + if (!$table->hasIndex('UNIQ_watch')) { + $table->addUniqueIndex(['poll_id', 'table'], 'UNIQ_watch'); + } + return $schema; + } +} diff --git a/lib/Service/CommentService.php b/lib/Service/CommentService.php index 90e71b23..2619311f 100644 --- a/lib/Service/CommentService.php +++ b/lib/Service/CommentService.php @@ -25,32 +25,38 @@ namespace OCA\Polls\Service; use OCA\Polls\Db\Comment; use OCA\Polls\Db\CommentMapper; +use OCA\Polls\Db\Watch; use OCA\Polls\Model\Acl; class CommentService { + /** @var Acl */ + private $acl; + + /** @var AnonymizeService */ + private $anonymizer; + /** @var CommentMapper */ private $commentMapper; /** @var Comment */ private $comment; - /** @var AnonymizeService */ - private $anonymizer; - - /** @var Acl */ - private $acl; + /** @var WatchService */ + private $watchService; public function __construct( + Acl $acl, + AnonymizeService $anonymizer, CommentMapper $commentMapper, Comment $comment, - AnonymizeService $anonymizer, - Acl $acl + WatchService $watchService ) { + $this->acl = $acl; + $this->anonymizer = $anonymizer; $this->commentMapper = $commentMapper; $this->comment = $comment; - $this->anonymizer = $anonymizer; - $this->acl = $acl; + $this->watchService = $watchService; } /** @@ -88,6 +94,7 @@ class CommentService { $this->comment->setDt(date('Y-m-d H:i:s')); $this->comment->setTimestamp(time()); $this->comment = $this->commentMapper->insert($this->comment); + $this->watchService->writeUpdate($this->comment->getPollId(), Watch::OBJECT_COMMENTS); return $this->comment; } @@ -104,6 +111,7 @@ class CommentService { } $this->commentMapper->delete($this->comment); + $this->watchService->writeUpdate($this->comment->getPollId(), Watch::OBJECT_COMMENTS); return $this->comment; } } diff --git a/lib/Service/OptionService.php b/lib/Service/OptionService.php index 4e09440a..51bf2c98 100644 --- a/lib/Service/OptionService.php +++ b/lib/Service/OptionService.php @@ -34,32 +34,38 @@ use OCA\Polls\Db\OptionMapper; use OCA\Polls\Db\Option; use OCA\Polls\Db\PollMapper; use OCA\Polls\Db\Poll; +use OCA\Polls\Db\Watch; use OCA\Polls\Model\Acl; class OptionService { - /** @var OptionMapper */ - private $optionMapper; + /** @var Acl */ + private $acl; /** @var Option */ private $option; + /** @var OptionMapper */ + private $optionMapper; + /** @var PollMapper */ private $pollMapper; - /** @var Acl */ - private $acl; + /** @var WatchService */ + private $watchService; public function __construct( - OptionMapper $optionMapper, + Acl $acl, Option $option, + OptionMapper $optionMapper, PollMapper $pollMapper, - Acl $acl + WatchService $watchService ) { - $this->optionMapper = $optionMapper; + $this->acl = $acl; $this->option = $option; + $this->optionMapper = $optionMapper; $this->pollMapper = $pollMapper; - $this->acl = $acl; + $this->watchService = $watchService; } /** @@ -116,10 +122,12 @@ class OptionService { $this->setOption($timestamp, $pollOptionText); try { - return $this->optionMapper->insert($this->option); + $this->option = $this->optionMapper->insert($this->option); + $this->watchService->writeUpdate($this->option->getPollId(), Watch::OBJECT_OPTIONS); } catch (UniqueConstraintViolationException $e) { throw new DuplicateEntryException('This option already exists'); } + return $this->option; } /** @@ -132,7 +140,9 @@ class OptionService { $this->acl->setPollId($this->option->getPollId())->request(Acl::PERMISSION_EDIT); $this->setOption($timestamp, $pollOptionText); - return $this->optionMapper->update($this->option); + $this->option = $this->optionMapper->update($this->option); + $this->watchService->writeUpdate($this->option->getPollId(), Watch::OBJECT_OPTIONS); + return $this->option; } /** @@ -144,6 +154,7 @@ class OptionService { $this->option = $this->optionMapper->find($optionId); $this->acl->setPollId($this->option->getPollId())->request(Acl::PERMISSION_EDIT); $this->optionMapper->delete($this->option); + $this->watchService->writeUpdate($this->option->getPollId(), Watch::OBJECT_OPTIONS); return $this->option; } @@ -163,7 +174,9 @@ class OptionService { $this->option->setConfirmed(time()); } - return $this->optionMapper->update($this->option); + $this->option = $this->optionMapper->update($this->option); + $this->watchService->writeUpdate($this->option->getPollId(), Watch::OBJECT_OPTIONS); + return $this->option; } /** @@ -197,6 +210,7 @@ class OptionService { \OC::$server->getLogger()->warning('skip adding ' . $baseDate->format('c') . 'for pollId' . $this->option->getPollId() . '. Option alredy exists.'); } } + $this->watchService->writeUpdate($this->option->getPollId(), Watch::OBJECT_OPTIONS); return $this->optionMapper->findByPoll($this->option->getPollId()); } @@ -251,6 +265,7 @@ class OptionService { } } + $this->watchService->writeUpdate($pollId, Watch::OBJECT_OPTIONS); return $this->optionMapper->findByPoll($pollId); } @@ -287,6 +302,7 @@ class OptionService { $this->optionMapper->update($option); } + $this->watchService->writeUpdate($this->option->getPollId(), Watch::OBJECT_OPTIONS); return $this->optionMapper->findByPoll($this->option->getPollId()); } diff --git a/lib/Service/PollService.php b/lib/Service/PollService.php index 71c21d26..e65e7120 100644 --- a/lib/Service/PollService.php +++ b/lib/Service/PollService.php @@ -30,12 +30,12 @@ use OCA\Polls\Exceptions\InvalidShowResultsException; use OCA\Polls\Exceptions\InvalidPollTypeException; use OCA\Polls\Exceptions\NotAuthorizedException; - use OCA\Polls\Db\Log; use OCA\Polls\Db\PollMapper; use OCA\Polls\Db\Poll; use OCA\Polls\Db\VoteMapper; use OCA\Polls\Db\Vote; +use OCA\Polls\Db\Watch; use OCA\Polls\Model\Acl; class PollService { @@ -52,6 +52,9 @@ class PollService { /** @var VoteMapper */ private $voteMapper; + /** @var WatchService */ + private $watchService; + /** @var Vote */ private $vote; @@ -73,6 +76,7 @@ class PollService { Poll $poll, VoteMapper $voteMapper, Vote $vote, + WatchService $watchService, LogService $logService, NotificationService $notificationService, MailService $mailService, @@ -82,6 +86,7 @@ class PollService { $this->userId = $UserId; $this->poll = $poll; $this->voteMapper = $voteMapper; + $this->watchService = $watchService; $this->vote = $vote; $this->logService = $logService; $this->notificationService = $notificationService; @@ -206,6 +211,7 @@ class PollService { $this->poll->setImportant(0); $this->poll = $this->pollMapper->insert($this->poll); + $this->watchService->writeUpdate($this->poll->getId(), Watch::OBJECT_POLLS); $this->logService->setLog($this->poll->getId(), Log::MSG_ID_ADDPOLL); return $this->poll; @@ -234,6 +240,7 @@ class PollService { $this->poll->deserializeArray($poll); $this->pollMapper->update($this->poll); + $this->watchService->writeUpdate($this->poll->getId(), Watch::OBJECT_POLLS); $this->logService->setLog($this->poll->getId(), Log::MSG_ID_UPDATEPOLL); return $this->poll; @@ -256,6 +263,7 @@ class PollService { } $this->poll = $this->pollMapper->update($this->poll); + $this->watchService->writeUpdate($this->poll->getId(), Watch::OBJECT_POLLS); $this->logService->setLog($this->poll->getId(), Log::MSG_ID_DELETEPOLL); if ($this->userId !== $this->poll->getOwner()) { @@ -283,6 +291,7 @@ class PollService { $this->acl->setPoll($this->poll)->request(Acl::PERMISSION_DELETE); $this->pollMapper->delete($this->poll); + $this->watchService->writeUpdate($this->poll->getId(), Watch::OBJECT_POLLS); if ($this->userId !== $this->poll->getOwner()) { // send notification to the original owner @@ -327,7 +336,9 @@ class PollService { $this->poll->setAdminAccess($origin->getAdminAccess()); $this->poll->setImportant($origin->getImportant()); - return $this->pollMapper->insert($this->poll); + $this->poll = $this->pollMapper->insert($this->poll); + $this->watchService->writeUpdate($this->poll->getId(), Watch::OBJECT_POLLS); + return $this->poll; } /** diff --git a/lib/Service/VoteService.php b/lib/Service/VoteService.php index f867c46f..415517c1 100644 --- a/lib/Service/VoteService.php +++ b/lib/Service/VoteService.php @@ -32,12 +32,22 @@ use OCA\Polls\Db\OptionMapper; use OCA\Polls\Db\VoteMapper; use OCA\Polls\Db\PollMapper; use OCA\Polls\Db\Vote; +use OCA\Polls\Db\Watch; use OCA\Polls\Model\Acl; class VoteService { - /** @var VoteMapper */ - private $voteMapper; + /** @var Acl */ + private $acl; + + /** @var AnonymizeService */ + private $anonymizer; + + /** @var LogService */ + private $logService; + + /** @var OptionMapper */ + private $optionMapper; /** @var PollMapper */ private $pollMapper; @@ -45,34 +55,31 @@ class VoteService { /** @var Vote */ private $vote; - /** @var OptionMapper */ - private $optionMapper; - - /** @var AnonymizeService */ - private $anonymizer; + /** @var VoteMapper */ + private $voteMapper; - /** @var LogService */ - private $logService; + /** @var WatchService */ + private $watchService; - /** @var Acl */ - private $acl; public function __construct( - VoteMapper $voteMapper, - PollMapper $pollMapper, - Vote $vote, - OptionMapper $optionMapper, + Acl $acl, AnonymizeService $anonymizer, LogService $logService, - Acl $acl + OptionMapper $optionMapper, + PollMapper $pollMapper, + Vote $vote, + VoteMapper $voteMapper, + WatchService $watchService ) { - $this->voteMapper = $voteMapper; - $this->pollMapper = $pollMapper; - $this->vote = $vote; - $this->optionMapper = $optionMapper; + $this->acl = $acl; $this->anonymizer = $anonymizer; $this->logService = $logService; - $this->acl = $acl; + $this->optionMapper = $optionMapper; + $this->pollMapper = $pollMapper; + $this->vote = $vote; + $this->voteMapper = $voteMapper; + $this->watchService = $watchService; } /** @@ -172,7 +179,8 @@ class VoteService { $this->vote->setVoteAnswer($setTo); $this->voteMapper->insert($this->vote); } - $this->logService->setLog($this->acl->getPollId(), Log::MSG_ID_SETVOTE, $this->vote->getUserId()); + $this->logService->setLog($this->vote->getPollId(), Log::MSG_ID_SETVOTE, $this->vote->getUserId()); + $this->watchService->writeUpdate($this->vote->getPollId(), Watch::OBJECT_VOTES); return $this->vote; } @@ -182,6 +190,7 @@ class VoteService { public function delete(int $pollId, string $userId): string { $this->acl->setPollId($pollId)->request(Acl::PERMISSION_EDIT); $this->voteMapper->deleteByPollAndUser($pollId, $userId); + $this->watchService->writeUpdate($pollId, Watch::OBJECT_VOTES); return $userId; } } diff --git a/lib/Service/WatchService.php b/lib/Service/WatchService.php new file mode 100644 index 00000000..362fa637 --- /dev/null +++ b/lib/Service/WatchService.php @@ -0,0 +1,72 @@ + + * + * @author René Gieling + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\Polls\Service; + +use OCP\AppFramework\Db\DoesNotExistException; + +use OCA\Polls\Db\Watch; +use OCA\Polls\Db\WatchMapper; + +class WatchService { + + /** @var WatchMapper */ + private $watchMapper; + + /** @var Watch */ + private $watch; + + public function __construct( + WatchMapper $watchMapper + ) { + $this->watchMapper = $watchMapper; + } + + /** + * @return Watch[] + */ + public function getUpdates(int $pollId, int $offset): array { + try { + return $this->watchMapper->findUpdatesForPollId($pollId, $offset); + } catch (DoesNotExistException $e) { + return []; + } + } + + /** + * @return Watch + */ + public function writeUpdate(int $pollId, string $table): Watch { + try { + $this->watch = $this->watchMapper->findForPollIdAndTable($pollId, $table); + } catch (DoesNotExistException $e) { + $this->watch = new Watch(); + $this->watch->setPollId($pollId); + $this->watch->setTable($table); + $this->watch = $this->watchMapper->insert($this->watch); + } + + $this->watch->setUpdated(time()); + return $this->watchMapper->update($this->watch); + } +} -- cgit v1.2.3