diff --git a/appinfo/database.xml b/appinfo/database.xml index 91c9076ba..a96137c42 100644 --- a/appinfo/database.xml +++ b/appinfo/database.xml @@ -46,6 +46,13 @@ false true + + last_modified + integer + + false + true + @@ -85,6 +92,13 @@ falsetrue + + last_modified + integer + + false + true + deck_stacks_board_id_index diff --git a/lib/Controller/BoardApiController.php b/lib/Controller/BoardApiController.php index d45644eef..9ee8db431 100644 --- a/lib/Controller/BoardApiController.php +++ b/lib/Controller/BoardApiController.php @@ -24,6 +24,7 @@ namespace OCA\Deck\Controller; +use OCA\Deck\Db\ChangeHelper; use OCP\AppFramework\ApiController; use OCP\AppFramework\Http; use OCP\AppFramework\Http\DataResponse; @@ -60,9 +61,14 @@ class BoardApiController extends ApiController { * Return all of the boards that the current user has access to. */ public function index() { - $boards = $this->service->findAll(); + $modified = $this->request->getHeader('If-Modified-Since'); + if ($modified === '') { + $boards = $this->service->findAll(); + } else { + $boards = $this->service->findAll(strtotime($modified)); + } return new DataResponse($boards, HTTP::STATUS_OK); - } + } /** * @NoAdminRequired @@ -87,8 +93,8 @@ class BoardApiController extends ApiController { * * Create a board with the specified title and color. */ - public function create($title, $color) { - $board = $this->service->create($title, $this->userId, $color); + public function create($title, $color) { + $board = $this->service->create($title, $this->userId, $color); return new DataResponse($board, HTTP::STATUS_OK); } @@ -96,14 +102,14 @@ class BoardApiController extends ApiController { * @NoAdminRequired * @CORS * @NoCSRFRequired - * + * * @params $title - * @params $color - * @params $archived + * @params $color + * @params $archived * * Update a board with the specified boardId, title and color, and archived state. */ - public function update($title, $color, $archived = false) { + public function update($title, $color, $archived = false) { $board = $this->service->update($this->request->getParam('boardId'), $title, $color, $archived); return new DataResponse($board, HTTP::STATUS_OK); } @@ -112,7 +118,7 @@ class BoardApiController extends ApiController { * @NoAdminRequired * @CORS * @NoCSRFRequired - * + * * * Delete the board specified by $boardId. Return the board that was deleted. */ @@ -125,12 +131,12 @@ class BoardApiController extends ApiController { * @NoAdminRequired * @CORS * @NoCSRFRequired - * + * * * Undo the deletion of the board specified by $boardId. */ - public function undoDelete() { - $board = $this->service->deleteUndo($this->request->getParam('boardId')); + public function undoDelete() { + $board = $this->service->deleteUndo($this->request->getParam('boardId')); return new DataResponse($board, HTTP::STATUS_OK); } diff --git a/lib/Controller/CardApiController.php b/lib/Controller/CardApiController.php index 5aa45d0f6..d1a4a8edc 100644 --- a/lib/Controller/CardApiController.php +++ b/lib/Controller/CardApiController.php @@ -23,6 +23,7 @@ namespace OCA\Deck\Controller; + use OCA\Deck\Db\ChangeHelper; use OCP\AppFramework\ApiController; use OCP\AppFramework\Http; use OCP\AppFramework\Http\DataResponse; @@ -35,8 +36,8 @@ * @package OCA\Deck\Controller */ class CardApiController extends ApiController { - private $cardService; - private $userId; + private $cardService; + private $userId; /** * @param string $appName @@ -45,9 +46,9 @@ class CardApiController extends ApiController { * @param $userId */ public function __construct($appName, IRequest $request, CardService $cardService, $userId) { - parent::__construct($appName, $request); - $this->cardService = $cardService; - $this->userId = $userId; + parent::__construct($appName, $request); + $this->cardService = $cardService; + $this->userId = $userId; } /** @@ -118,8 +119,8 @@ class CardApiController extends ApiController { /** * @NoAdminRequired * @CORS - * @NoCSRFRequired - * + * @NoCSRFRequired + * * Assign a label to a card. */ public function removeLabel($labelId) { @@ -130,8 +131,8 @@ class CardApiController extends ApiController { /** * @NoAdminRequired * @CORS - * @NoCSRFRequired - * + * @NoCSRFRequired + * * Unassign a label to a card. */ public function unassignUser($userId) { @@ -147,12 +148,12 @@ class CardApiController extends ApiController { /** * @NoAdminRequired * @CORS - * @NoCSRFRequired - * + * @NoCSRFRequired + * * Unassign a label to a card. */ public function reorder($stackId, $order) { $card = $this->cardService->reorder($this->request->getParam('cardId'), $stackId, $order); return new DataResponse($card, HTTP::STATUS_OK); } -} \ No newline at end of file +} diff --git a/lib/Controller/StackApiController.php b/lib/Controller/StackApiController.php index 6e4fb3867..021fae9c2 100644 --- a/lib/Controller/StackApiController.php +++ b/lib/Controller/StackApiController.php @@ -49,7 +49,7 @@ class StackApiController extends ApiController { public function __construct($appName, IRequest $request, StackService $stackService, BoardService $boardService) { parent::__construct($appName, $request); $this->stackService = $stackService; - $this->boardService = $boardService; + $this->boardService = $boardService; } /** @@ -59,8 +59,13 @@ class StackApiController extends ApiController { * * Return all of the stacks in the specified board. */ - public function index() { - $stacks = $this->stackService->findAll($this->request->getParam('boardId')); + public function index() { + $since = 0; + $modified = $this->request->getHeader('If-Modified-Since'); + if ($modified !== '') { + $since = strtotime($modified); + } + $stacks = $this->stackService->findAll($this->request->getParam('boardId'), $since); return new DataResponse($stacks, HTTP::STATUS_OK); } @@ -75,7 +80,7 @@ class StackApiController extends ApiController { $stack = $this->stackService->find($this->request->getParam('stackId')); return new DataResponse($stack, HTTP::STATUS_OK); } - + /** * @NoAdminRequired * @CORS @@ -95,13 +100,13 @@ class StackApiController extends ApiController { * @NoAdminRequired * @CORS * @NoCSRFRequired - * - * @params $title + * + * @params $title * @params $order * * Update a stack by the specified stackId and boardId with the values that were put. */ - public function update($title, $order) { + public function update($title, $order) { $stack = $this->stackService->update($this->request->getParam('stackId'), $title, $this->request->getParam('boardId'), $order, 0); return new DataResponse($stack, HTTP::STATUS_OK); } @@ -113,7 +118,7 @@ class StackApiController extends ApiController { * * Delete the stack specified by $this->request->getParam('stackId'). */ - public function delete() { + public function delete() { $stack = $this->stackService->delete($this->request->getParam('stackId')); return new DataResponse($stack, HTTP::STATUS_OK); } diff --git a/lib/Db/Board.php b/lib/Db/Board.php index 905c4af62..fabf39061 100644 --- a/lib/Db/Board.php +++ b/lib/Db/Board.php @@ -5,20 +5,20 @@ * @author Julius Härtl * * @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\Deck\Db; @@ -35,12 +35,14 @@ class Board extends RelationalEntity { protected $users = []; protected $shared; protected $deletedAt = 0; + protected $lastModified = 0; public function __construct() { $this->addType('id', 'integer'); $this->addType('shared', 'integer'); $this->addType('archived', 'boolean'); $this->addType('deletedAt', 'integer'); + $this->addType('lastModified', 'integer'); $this->addRelation('labels'); $this->addRelation('acl'); $this->addRelation('shared'); @@ -75,4 +77,4 @@ class Board extends RelationalEntity { $this->acl[$a->id] = $a; } } -} \ No newline at end of file +} diff --git a/lib/Db/BoardMapper.php b/lib/Db/BoardMapper.php index 594c9b8ce..fddff1ac6 100644 --- a/lib/Db/BoardMapper.php +++ b/lib/Db/BoardMapper.php @@ -62,7 +62,7 @@ class BoardMapper extends DeckMapper implements IPermissionMapper { * @throws DoesNotExistException */ public function find($id, $withLabels = false, $withAcl = false) { - $sql = 'SELECT id, title, owner, color, archived, deleted_at FROM `*PREFIX*deck_boards` ' . + $sql = 'SELECT id, title, owner, color, archived, deleted_at, last_modified FROM `*PREFIX*deck_boards` ' . 'WHERE `id` = ?'; $board = $this->findEntity($sql, [$id]); @@ -89,11 +89,14 @@ class BoardMapper extends DeckMapper implements IPermissionMapper { * @param null $offset * @return array */ - public function findAllByUser($userId, $limit = null, $offset = null) { - $sql = 'SELECT id, title, owner, color, archived, deleted_at, 0 as shared FROM `*PREFIX*deck_boards` WHERE owner = ? UNION ' . - 'SELECT boards.id, title, owner, color, archived, deleted_at, 1 as shared FROM `*PREFIX*deck_boards` as boards ' . - 'JOIN `*PREFIX*deck_board_acl` as acl ON boards.id=acl.board_id WHERE acl.participant=? AND acl.type=? AND boards.owner != ?'; - $entries = $this->findEntities($sql, [$userId, $userId, Acl::PERMISSION_TYPE_USER, $userId], $limit, $offset); + public function findAllByUser($userId, $limit = null, $offset = null, $since = 0) { + $sql = 'SELECT id, title, owner, color, archived, deleted_at, 0 as shared, last_modified FROM `*PREFIX*deck_boards` WHERE owner = ? AND last_modified > ?'; + $entries = $this->findEntities($sql, [$userId, $since], $limit, $offset); + + $sql = 'SELECT id, title, owner, color, archived, deleted_at, 0 as shared, last_modified FROM `*PREFIX*deck_boards` WHERE owner = ? AND last_modified > ? UNION ' . + 'SELECT boards.id, title, owner, color, archived, deleted_at, 1 as shared, last_modified FROM `*PREFIX*deck_boards` as boards ' . + 'JOIN `*PREFIX*deck_board_acl` as acl ON boards.id=acl.board_id WHERE acl.participant=? AND acl.type=? AND boards.owner != ? AND last_modified > ?'; + $entries = $this->findEntities($sql, [$userId, $since, $userId, Acl::PERMISSION_TYPE_USER, $userId, $since], $limit, $offset); /* @var Board $entry */ foreach ($entries as $entry) { $acl = $this->aclMapper->findAll($entry->id); @@ -115,7 +118,7 @@ class BoardMapper extends DeckMapper implements IPermissionMapper { if (count($groups) <= 0) { return []; } - $sql = 'SELECT boards.id, title, owner, color, archived, deleted_at, 2 as shared FROM `*PREFIX*deck_boards` as boards ' . + $sql = 'SELECT boards.id, title, owner, color, archived, deleted_at, 2 as shared, last_modified FROM `*PREFIX*deck_boards` as boards ' . 'INNER JOIN `*PREFIX*deck_board_acl` as acl ON boards.id=acl.board_id WHERE owner != ? AND type=? AND ('; for ($i = 0, $iMax = count($groups); $i < $iMax; $i++) { $sql .= 'acl.participant = ? '; @@ -141,7 +144,7 @@ class BoardMapper extends DeckMapper implements IPermissionMapper { public function findToDelete() { // add buffer of 5 min $timeLimit = time() - (60 * 5); - $sql = 'SELECT id, title, owner, color, archived, deleted_at FROM `*PREFIX*deck_boards` ' . + $sql = 'SELECT id, title, owner, color, archived, deleted_at, last_modified FROM `*PREFIX*deck_boards` ' . 'WHERE `deleted_at` > 0 AND `deleted_at` < ?'; return $this->findEntities($sql, [$timeLimit]); } diff --git a/lib/Db/CardMapper.php b/lib/Db/CardMapper.php index 31f363398..9cbcc49b6 100644 --- a/lib/Db/CardMapper.php +++ b/lib/Db/CardMapper.php @@ -118,10 +118,10 @@ class CardMapper extends DeckMapper implements IPermissionMapper { return $card; } - public function findAll($stackId, $limit = null, $offset = null) { + public function findAll($stackId, $limit = null, $offset = null, $since = -1) { $sql = 'SELECT * FROM `*PREFIX*deck_cards` - WHERE `stack_id` = ? AND NOT archived AND deleted_at = 0 ORDER BY `order`'; - return $this->findEntities($sql, [$stackId], $limit, $offset); + WHERE `stack_id` = ? AND NOT archived AND deleted_at = 0 AND last_modified > ? ORDER BY `order`'; + return $this->findEntities($sql, [$stackId, $since], $limit, $offset); } public function findDeleted($boardId, $limit = null, $offset = null) { diff --git a/lib/Db/Stack.php b/lib/Db/Stack.php index f1ba66c9c..9c5eb4bf9 100644 --- a/lib/Db/Stack.php +++ b/lib/Db/Stack.php @@ -30,11 +30,13 @@ class Stack extends RelationalEntity { protected $deletedAt = 0; protected $cards = array(); protected $order; + protected $lastModified = 0; public function __construct() { $this->addType('id', 'integer'); $this->addType('boardId', 'integer'); $this->addType('deletedAt', 'integer'); + $this->addType('lastModified', 'integer'); $this->addType('order', 'integer'); } diff --git a/lib/Service/AttachmentService.php b/lib/Service/AttachmentService.php index 8c7a81307..58681a4d5 100644 --- a/lib/Service/AttachmentService.php +++ b/lib/Service/AttachmentService.php @@ -31,6 +31,7 @@ use OCA\Deck\Db\Acl; use OCA\Deck\Db\Attachment; use OCA\Deck\Db\AttachmentMapper; use OCA\Deck\Db\CardMapper; +use OCA\Deck\Db\ChangeHelper; use OCA\Deck\InvalidAttachmentType; use OCA\Deck\NoPermissionException; use OCA\Deck\NotFoundException; @@ -69,7 +70,7 @@ class AttachmentService { * @param IL10N $l10n * @throws \OCP\AppFramework\QueryException */ - public function __construct(AttachmentMapper $attachmentMapper, CardMapper $cardMapper, PermissionService $permissionService, Application $application, ICacheFactory $cacheFactory, $userId, IL10N $l10n, ActivityManager $activityManager) { + public function __construct(AttachmentMapper $attachmentMapper, CardMapper $cardMapper, ChangeHelper $changeHelper, PermissionService $permissionService, Application $application, ICacheFactory $cacheFactory, $userId, IL10N $l10n, ActivityManager $activityManager) { $this->attachmentMapper = $attachmentMapper; $this->cardMapper = $cardMapper; $this->permissionService = $permissionService; @@ -78,6 +79,7 @@ class AttachmentService { $this->cache = $cacheFactory->createDistributed('deck-card-attachments-'); $this->l10n = $l10n; $this->activityManager = $activityManager; + $this->changeHelper = $changeHelper; // Register shipped attachment services // TODO: move this to a plugin based approach once we have different types of attachments @@ -205,6 +207,7 @@ class AttachmentService { } catch (InvalidAttachmentType $e) { // just store the data } + $this->changeHelper->cardChanged($attachment->getCardId()); $this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_CARD, $attachment, ActivityManager::SUBJECT_ATTACHMENT_CREATE); return $attachment; } @@ -290,6 +293,7 @@ class AttachmentService { } catch (InvalidAttachmentType $e) { // just store the data } + $this->changeHelper->cardChanged($attachment->getCardId()); $this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_CARD, $attachment, ActivityManager::SUBJECT_ATTACHMENT_UPDATE); return $attachment; } @@ -332,6 +336,7 @@ class AttachmentService { // just delete without further action } $attachment = $this->attachmentMapper->delete($attachment); + $this->changeHelper->cardChanged($attachment->getCardId()); $this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_CARD, $attachment, ActivityManager::SUBJECT_ATTACHMENT_DELETE); return $attachment; } @@ -355,6 +360,7 @@ class AttachmentService { if ($service->allowUndo()) { $attachment->setDeletedAt(0); $attachment = $this->attachmentMapper->update($attachment); + $this->changeHelper->cardChanged($attachment->getCardId()); $this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_CARD, $attachment, ActivityManager::SUBJECT_ATTACHMENT_RESTORE); return $attachment; } diff --git a/lib/Service/BoardService.php b/lib/Service/BoardService.php index b421a46d8..e365fd583 100644 --- a/lib/Service/BoardService.php +++ b/lib/Service/BoardService.php @@ -84,10 +84,10 @@ class BoardService { /** * @return array */ - public function findAll() { + public function findAll($since = 0) { $userInfo = $this->getBoardPrerequisites(); - $userBoards = $this->boardMapper->findAllByUser($userInfo['user']); - $groupBoards = $this->boardMapper->findAllByGroups($userInfo['user'], $userInfo['groups']); + $userBoards = $this->boardMapper->findAllByUser($userInfo['user'], null, null, $since); + $groupBoards = $this->boardMapper->findAllByGroups($userInfo['user'], $userInfo['groups'],null, null, $since); $complete = array_merge($userBoards, $groupBoards); $result = []; foreach ($complete as &$item) { diff --git a/lib/Service/CardService.php b/lib/Service/CardService.php index 0cf6cfa60..1bebfa282 100644 --- a/lib/Service/CardService.php +++ b/lib/Service/CardService.php @@ -30,6 +30,7 @@ use OCA\Deck\Db\AssignedUsersMapper; use OCA\Deck\Db\Card; use OCA\Deck\Db\CardMapper; use OCA\Deck\Db\Acl; +use OCA\Deck\Db\ChangeHelper; use OCA\Deck\Db\StackMapper; use OCA\Deck\Notification\NotificationHelper; use OCA\Deck\Db\BoardMapper; @@ -54,6 +55,7 @@ class CardService { private $currentUser; private $activityManager; private $commentsManager; + private $changeHelper; public function __construct( CardMapper $cardMapper, @@ -68,6 +70,7 @@ class CardService { ActivityManager $activityManager, ICommentsManager $commentsManager, IUserManager $userManager, + ChangeHelper $changeHelper, $userId ) { $this->cardMapper = $cardMapper; @@ -82,6 +85,7 @@ class CardService { $this->activityManager = $activityManager; $this->commentsManager = $commentsManager; $this->userManager = $userManager; + $this->changeHelper = $changeHelper; $this->currentUser = $userId; } @@ -176,6 +180,7 @@ class CardService { $card->setOwner($owner); $card = $this->cardMapper->insert($card); $this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_CARD, $card, ActivityManager::SUBJECT_CARD_CREATE); + $this->changeHelper->cardChanged($card->getId(), false); return $card; } @@ -202,6 +207,7 @@ class CardService { $card->setDeletedAt(time()); $this->cardMapper->update($card); $this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_CARD, $card, ActivityManager::SUBJECT_CARD_DELETE); + $this->changeHelper->cardChanged($card->getId(), false); return $card; } @@ -263,6 +269,7 @@ class CardService { $changes->setAfter($card); $card = $this->cardMapper->update($card); $this->activityManager->triggerUpdateEvents(ActivityManager::DECK_OBJECT_CARD, $changes, ActivityManager::SUBJECT_CARD_UPDATE); + $this->changeHelper->cardChanged($card->getId(), false); return $card; } @@ -295,6 +302,7 @@ class CardService { throw new StatusException('Operation not allowed. This card is archived.'); } $card->setTitle($title); + $this->changeHelper->cardChanged($card->getId(), false); return $this->cardMapper->update($card); } @@ -349,7 +357,7 @@ class CardService { $this->cardMapper->update($card); $result[$card->getOrder()] = $card; } - + $this->changeHelper->cardChanged($id, false); return $result; } @@ -376,6 +384,7 @@ class CardService { $card->setArchived(true); $newCard = $this->cardMapper->update($card); $this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_CARD, $newCard, ActivityManager::SUBJECT_CARD_UPDATE_ARCHIVE); + $this->changeHelper->cardChanged($id, false); return $newCard; } @@ -402,6 +411,7 @@ class CardService { $card->setArchived(false); $newCard = $this->cardMapper->update($card); $this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_CARD, $newCard, ActivityManager::SUBJECT_CARD_UPDATE_UNARCHIVE); + $this->changeHelper->cardChanged($id, false); return $newCard; } @@ -434,6 +444,7 @@ class CardService { } $label = $this->labelMapper->find($labelId); $this->cardMapper->assignLabel($cardId, $labelId); + $this->changeHelper->cardChanged($cardId, false); $this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_CARD, $card, ActivityManager::SUBJECT_LABEL_ASSIGN, ['label' => $label]); } @@ -466,6 +477,7 @@ class CardService { } $label = $this->labelMapper->find($labelId); $this->cardMapper->removeLabel($cardId, $labelId); + $this->changeHelper->cardChanged($cardId, false); $this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_CARD, $card, ActivityManager::SUBJECT_LABEL_UNASSING, ['label' => $label]); } @@ -505,6 +517,7 @@ class CardService { $assignment->setCardId($cardId); $assignment->setParticipant($userId); $assignment = $this->assignedUsersMapper->insert($assignment); + $this->changeHelper->cardChanged($cardId, false); $this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_CARD, $card, ActivityManager::SUBJECT_CARD_USER_ASSIGN, ['assigneduser' => $userId]); return $assignment; } @@ -535,6 +548,7 @@ class CardService { $assignment = $this->assignedUsersMapper->delete($assignment); $card = $this->cardMapper->find($cardId); $this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_CARD, $card, ActivityManager::SUBJECT_CARD_USER_UNASSIGN, ['assigneduser' => $userId]); + $this->changeHelper->cardChanged($cardId, false); return $assignment; } } diff --git a/lib/Service/StackService.php b/lib/Service/StackService.php index 0ee599133..cbefec749 100644 --- a/lib/Service/StackService.php +++ b/lib/Service/StackService.php @@ -74,10 +74,10 @@ class StackService { $this->activityManager = $activityManager; } - private function enrichStackWithCards($stack) { - $cards = $this->cardMapper->findAll($stack->getId()); + private function enrichStackWithCards($stack, $since = -1) { + $cards = $this->cardMapper->findAll($stack->getId(), null, null, $since); - if(is_null($cards)) { + if(\count($cards) === 0) { return; } @@ -88,9 +88,9 @@ class StackService { $stack->setCards($cards); } - private function enrichStacksWithCards($stacks) { + private function enrichStacksWithCards($stacks, $since = -1) { foreach ($stacks as $stack) { - $this->enrichStackWithCards($stack); + $this->enrichStackWithCards($stack, $since); } } @@ -123,14 +123,14 @@ class StackService { * @throws \OCA\Deck\NoPermissionException * @throws BadRequestException */ - public function findAll($boardId) { + public function findAll($boardId, $since = 0) { if (is_numeric($boardId) === false) { throw new BadRequestException('boardId must be a number'); } $this->permissionService->checkPermission(null, $boardId, Acl::PERMISSION_READ); $stacks = $this->stackMapper->findAll($boardId); - $this->enrichStacksWithCards($stacks); + $this->enrichStacksWithCards($stacks, $since); return $stacks; }