From ee5a54a57542eed2f93c043ebd4472ec61a75b96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julius=20H=C3=A4rtl?= Date: Tue, 12 Jun 2018 15:33:06 +0200 Subject: [PATCH] Allow to undo file deletions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Julius Härtl --- appinfo/routes.php | 2 ++ css/style.scss | 4 +++ js/controller/CardController.js | 2 -- js/service/CardService.js | 31 +++++++++++++++++++--- lib/Controller/AttachmentController.php | 4 +++ lib/Cron/DeleteCron.php | 29 ++++++++++++++++----- lib/Db/Attachment.php | 6 ++--- lib/Db/AttachmentMapper.php | 18 +++++++++++++ lib/Service/AttachmentService.php | 34 +++++++++++++++++++++++-- lib/Service/FileService.php | 18 +++++++++++++ lib/Service/IAttachmentService.php | 15 +++++++++++ templates/part.card.php | 9 ++++--- 12 files changed, 152 insertions(+), 20 deletions(-) diff --git a/appinfo/routes.php b/appinfo/routes.php index b139ca638..d0766934c 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -64,6 +64,8 @@ return [ ['name' => 'attachment#create', 'url' => '/cards/{cardId}/attachment', 'verb' => 'POST'], ['name' => 'attachment#update', 'url' => '/cards/{cardId}/attachment/{attachmentId}', 'verb' => 'UPDATE'], ['name' => 'attachment#delete', 'url' => '/cards/{cardId}/attachment/{attachmentId}', 'verb' => 'DELETE'], + ['name' => 'attachment#restore', 'url' => '/cards/{cardId}/attachment/{attachmentId}/restore', 'verb' => 'GET'], + // labels ['name' => 'label#create', 'url' => '/labels', 'verb' => 'POST'], diff --git a/css/style.scss b/css/style.scss index ed959e35f..1eeaa5974 100644 --- a/css/style.scss +++ b/css/style.scss @@ -780,6 +780,10 @@ input.input-inline { li.attachment { display: flex; + &.deleted { + opacity: .5; + } + .fileicon { display: inline-block; min-width: 32px; diff --git a/js/controller/CardController.js b/js/controller/CardController.js index 171bb641d..9bbd9bb51 100644 --- a/js/controller/CardController.js +++ b/js/controller/CardController.js @@ -50,11 +50,9 @@ app.controller('CardController', function ($scope, $rootScope, $sce, $location, $scope.uploader.uploadItem(fileItem); }; $scope.uploader.onAfterAddingFile = function(fileItem) { - console.log(fileItem); let existingFile = $scope.cardservice.getCurrent().attachments.find((attachment) => { return attachment.data === fileItem.file.name; }); - console.log(existingFile); if (typeof existingFile !== 'undefined') { OC.dialogs.confirm( `A file with the name ${fileItem.file.name} already exists. Do you want to overwrite it?`, diff --git a/js/service/CardService.js b/js/service/CardService.js index 7e4f6c393..324e8ed45 100644 --- a/js/service/CardService.js +++ b/js/service/CardService.js @@ -137,9 +137,18 @@ app.factory('CardService', function (ApiService, $http, $q) { var deferred = $q.defer(); var self = this; $http.delete(this.baseUrl + '/' + this.getCurrent().id + '/attachment/' + attachment.id, {}).then(function (response) { - self.getCurrent().attachments = self.getCurrent().attachments.filter(function (obj) { - return obj.id !== attachment.id; - }); + if (response.data.de#letedAt > 0) { + let currentAttachment = self.getCurrent().attachments.find(function (obj) { + if (obj.id === attachment.id) { + obj.deletedAt = response.data.deletedAt; + } + }); + + } else { + self.getCurrent().attachments = self.getCurrent().attachments.filter(function (obj) { + return obj.id !== attachment.id; + }); + } deferred.resolve(response.data); }, function (error) { deferred.reject('Error when removing the attachment'); @@ -147,6 +156,22 @@ app.factory('CardService', function (ApiService, $http, $q) { return deferred.promise; }; + CardService.prototype.attachmentRemoveUndo = function (attachment) { + var deferred = $q.defer(); + var self = this; + $http.get(this.baseUrl + '/' + this.getCurrent().id + '/attachment/' + attachment.id + '/restore', {}).then(function (response) { + let currentAttachment = self.getCurrent().attachments.find(function (obj) { + if (obj.id === attachment.id) { + obj.deletedAt = response.data.deletedAt; + } + }); + deferred.resolve(response.data); + }, function (error) { + deferred.reject('Error when restoring the attachment'); + }); + return deferred.promise; + }; + var service = new CardService($http, 'cards', $q); return service; }); \ No newline at end of file diff --git a/lib/Controller/AttachmentController.php b/lib/Controller/AttachmentController.php index 096c4a3bc..d64a5c7d3 100644 --- a/lib/Controller/AttachmentController.php +++ b/lib/Controller/AttachmentController.php @@ -68,4 +68,8 @@ class AttachmentController extends Controller { public function delete($cardId, $attachmentId) { return $this->attachmentService->delete($cardId, $attachmentId); } + + public function restore($cardId, $attachmentId) { + return $this->attachmentService->restore($cardId, $attachmentId); + } } \ No newline at end of file diff --git a/lib/Cron/DeleteCron.php b/lib/Cron/DeleteCron.php index 0d769deef..c78d58e02 100644 --- a/lib/Cron/DeleteCron.php +++ b/lib/Cron/DeleteCron.php @@ -21,25 +21,28 @@ * */ -/** - * Created by PhpStorm. - * User: jus - * Date: 16.05.17 - * Time: 12:34 - */ namespace OCA\Deck\Cron; use OC\BackgroundJob\Job; +use OCA\Deck\Db\AttachmentMapper; use OCA\Deck\Db\BoardMapper; +use OCA\Deck\InvalidAttachmentType; +use OCA\Deck\Service\AttachmentService; class DeleteCron extends Job { /** @var BoardMapper */ private $boardMapper; + /** @var AttachmentService */ + private $attachmentService; + /** @var AttachmentMapper */ + private $attachmentMapper; - public function __construct(BoardMapper $boardMapper) { + public function __construct(BoardMapper $boardMapper, AttachmentService $attachmentService, AttachmentMapper $attachmentMapper) { $this->boardMapper = $boardMapper; + $this->attachmentService = $attachmentService; + $this->attachmentMapper = $attachmentMapper; } /** @@ -51,6 +54,18 @@ class DeleteCron extends Job { foreach ($boards as $board) { $this->boardMapper->delete($board); } + + $attachments = $this->attachmentMapper->findToDelete(); + foreach ($attachments as $attachment) { + try { + $service = $this->attachmentService->getService($attachment->getType()); + $service->delete($attachment); + } catch (InvalidAttachmentType $e) { + // Just delete the attachment if no service is available + } + $this->attachmentMapper->delete($attachment); + } + } } \ No newline at end of file diff --git a/lib/Db/Attachment.php b/lib/Db/Attachment.php index cc1ce7abc..2f8f7b071 100644 --- a/lib/Db/Attachment.php +++ b/lib/Db/Attachment.php @@ -28,10 +28,10 @@ class Attachment extends RelationalEntity { protected $cardId; protected $type; protected $data; - protected $lastModified; - protected $createdAt; + protected $lastModified = 0; + protected $createdAt = 0; protected $createdBy; - protected $deletedAt; + protected $deletedAt = 0; protected $extendedData = []; public function __construct() { diff --git a/lib/Db/AttachmentMapper.php b/lib/Db/AttachmentMapper.php index a67174f24..7bb125723 100644 --- a/lib/Db/AttachmentMapper.php +++ b/lib/Db/AttachmentMapper.php @@ -82,6 +82,24 @@ class AttachmentMapper extends DeckMapper implements IPermissionMapper { return $entities; } + public function findToDelete() { + // add buffer of 5 min + $timeLimit = time() - (60 * 5); + $qb = $this->db->getQueryBuilder(); + $qb->select('*') + ->from('deck_attachment') + ->where($qb->expr()->gt('deleted_at', '0', IQueryBuilder::PARAM_INT)) + ->andWhere($qb->expr()->lt('deleted_at', (string)$timeLimit, IQueryBuilder::PARAM_INT)); + + $entities = []; + $cursor = $qb->execute(); + while($row = $cursor->fetch()){ + $entities[] = $this->mapRowToEntity($row); + } + $cursor->closeCursor(); + return $entities; + } + /** * @param Attachment $attachment * @throws \Exception diff --git a/lib/Service/AttachmentService.php b/lib/Service/AttachmentService.php index 5d50fb5d8..3155d650d 100644 --- a/lib/Service/AttachmentService.php +++ b/lib/Service/AttachmentService.php @@ -30,6 +30,7 @@ use OCA\Deck\Db\Attachment; use OCA\Deck\Db\AttachmentMapper; use OCA\Deck\Db\CardMapper; use OCA\Deck\InvalidAttachmentType; +use OCA\Deck\NoPermissionException; use OCA\Deck\NotFoundException; use OCP\AppFramework\Http\Response; @@ -181,17 +182,46 @@ class AttachmentService { return $attachment; } + /** + * Either mark an attachment as deleted for later removal or just remove it depending + * on the IAttachmentService implementation + * + * @param $cardId + * @param $attachmentId + * @return \OCP\AppFramework\Db\Entity + * @throws \OCA\Deck\NoPermissionException + * @throws \OCP\AppFramework\Db\DoesNotExistException + * @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException + */ public function delete($cardId, $attachmentId) { $this->permissionService->checkPermission($this->cardMapper, $cardId, Acl::PERMISSION_EDIT); $attachment = $this->attachmentMapper->find($attachmentId); try { $service = $this->getService($attachment->getType()); + if ($service->allowUndo()) { + $service->markAsDeleted($attachment); + return $this->attachmentMapper->update($attachment); + } $service->delete($attachment); } catch (InvalidAttachmentType $e) { // just delete without further action } - $this->attachmentMapper->delete($attachment); - return $attachment; + return $this->attachmentMapper->delete($attachment); + } + + public function restore($cardId, $attachmentId) { + $this->permissionService->checkPermission($this->cardMapper, $cardId, Acl::PERMISSION_EDIT); + + $attachment = $this->attachmentMapper->find($attachmentId); + try { + $service = $this->getService($attachment->getType()); + if ($service->allowUndo()) { + $attachment->setDeletedAt(0); + return $this->attachmentMapper->update($attachment); + } + } catch (InvalidAttachmentType $e) { + } + throw new NoPermissionException(); } } \ No newline at end of file diff --git a/lib/Service/FileService.php b/lib/Service/FileService.php index 417027e21..d5f14c4f5 100644 --- a/lib/Service/FileService.php +++ b/lib/Service/FileService.php @@ -149,4 +149,22 @@ class FileService implements IAttachmentService { $response->addHeader('Content-Type', $file->getMimeType()); return $response; } + + /** + * Should undo be allowed and the delete action be done by a background job + * + * @return bool + */ + public function allowUndo() { + return true; + } + + /** + * Mark an attachment as deleted + * + * @param Attachment $attachment + */ + public function markAsDeleted(Attachment $attachment) { + $attachment->setDeletedAt(time()); + } } \ No newline at end of file diff --git a/lib/Service/IAttachmentService.php b/lib/Service/IAttachmentService.php index 1210637a6..ae796b5f3 100644 --- a/lib/Service/IAttachmentService.php +++ b/lib/Service/IAttachmentService.php @@ -81,4 +81,19 @@ interface IAttachmentService { * @param Attachment $attachment */ public function delete(Attachment $attachment); + + /** + * Should undo be allowed and the delete action be done by a background job + * + * @return bool + */ + public function allowUndo(); + + /** + * Mark an attachment as deleted + * + * @param Attachment $attachment + */ + public function markAsDeleted(Attachment $attachment); + } \ No newline at end of file diff --git a/templates/part.card.php b/templates/part.card.php index 90b8e6cf6..dff818da8 100644 --- a/templates/part.card.php +++ b/templates/part.card.php @@ -94,7 +94,7 @@