Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New attempt to implement recycle bin #2161

Merged
merged 23 commits into from
Jun 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
96d10a1
feat: Implement recycle bin
marcelklehr May 24, 2024
4041e40
fix: Make it pass tests again
marcelklehr May 25, 2024
b73a1c8
fix: Make it pass tests again
marcelklehr May 25, 2024
b524c69
fix: Make it pass tests again
marcelklehr May 25, 2024
f63178e
feat: Add help and project support sections to settings
marcelklehr May 25, 2024
c4a28de
fix: Make it pass tests again
marcelklehr May 26, 2024
b6b2131
feat: Continue trashbin php work
marcelklehr May 26, 2024
f80bce5
feat: Implement Trashbin UI
marcelklehr May 26, 2024
6dab1f7
feat: Implement restoring folders from trash bin
marcelklehr May 29, 2024
27d1372
feat: Implement restoring bookmarks from trash bin
marcelklehr May 29, 2024
efc8eac
fix: Don't allow selection inside trash bin
marcelklehr May 29, 2024
df3d48c
fix: Tag count
marcelklehr May 29, 2024
295b9a2
fix: Don't allow dragging folders inside trashbin
marcelklehr May 29, 2024
ba4253d
fix: Show original folders for trashbin items
marcelklehr May 29, 2024
a635329
feat: Auto-empty trash bin after two months
marcelklehr May 31, 2024
512be0a
fix: Notify about soft delete activity instead of hard delete
marcelklehr May 31, 2024
9cae6c3
perf: speed up initial page load
marcelklehr May 31, 2024
856af75
perf: speed up getSubFolders for deleted folders
marcelklehr May 31, 2024
49a3f1e
perf(TreeCacheManager): Increase cache lifetime
marcelklehr May 31, 2024
4df7753
fix: eslint fix
marcelklehr Jun 1, 2024
ffea417
fix: tests
marcelklehr Jun 1, 2024
26694fd
fix: psalm issues
marcelklehr Jun 1, 2024
b6a6b48
fix: psalm issues
marcelklehr Jun 1, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions appinfo/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
['name' => 'web_view#index', 'url' => '/duplicated', 'verb' => 'GET', 'postfix' => 'duplicated'],
['name' => 'web_view#index', 'url' => '/shared', 'verb' => 'GET', 'postfix' => 'shared'],
['name' => 'web_view#index', 'url' => '/bookmarklet', 'verb' => 'GET', 'postfix' => 'bookmarklet'],
['name' => 'web_view#index', 'url' => '/trashbin', 'verb' => 'GET', 'postfix' => 'trashbin'],
['name' => 'web_view#service_worker', 'url' => '/service-worker.js', 'verb' => 'GET'],
['name' => 'web_view#manifest', 'url' => '/manifest.webmanifest', 'verb' => 'GET'],

Expand All @@ -45,6 +46,7 @@
['name' => 'internal_bookmark#count_unavailable', 'url' => '/bookmark/unavailable', 'verb' => 'GET'],
['name' => 'internal_bookmark#count_archived', 'url' => '/bookmark/archived', 'verb' => 'GET'],
['name' => 'internal_bookmark#count_duplicated', 'url' => '/bookmark/duplicated', 'verb' => 'GET'],
['name' => 'internal_bookmark#get_deleted_bookmarks', 'url' => '/bookmark/deleted', 'verb' => 'GET'],
['name' => 'internal_bookmark#edit_bookmark', 'url' => '/bookmark/{id}', 'verb' => 'PUT'],
['name' => 'internal_bookmark#get_single_bookmark', 'url' => '/bookmark/{id}', 'verb' => 'GET'],
['name' => 'internal_bookmark#delete_bookmark', 'url' => '/bookmark/{id}', 'verb' => 'DELETE'],
Expand All @@ -62,17 +64,20 @@
['name' => 'internal_tags#delete_tag', 'url' => '/tag/{old_name}', 'verb' => 'DELETE'],
['name' => 'internal_folders#get_folders', 'url' => '/folder', 'verb' => 'GET'],
['name' => 'internal_folders#find_shared_folders', 'url' => '/folder/shared', 'verb' => 'GET'],
['name' => 'internal_folders#get_deleted_folders', 'url' => '/folder/deleted', 'verb' => 'GET'],
['name' => 'internal_folders#get_folder', 'url' => '/folder/{folderId}', 'verb' => 'GET'],
['name' => 'internal_folders#add_folder', 'url' => '/folder', 'verb' => 'POST'],
['name' => 'internal_folders#edit_folder', 'url' => '/folder/{folderId}', 'verb' => 'PUT'],
['name' => 'internal_folders#delete_folder', 'url' => '/folder/{folderId}', 'verb' => 'DELETE'],
['name' => 'internal_folders#hash_folder', 'url' => '/folder/{folderId}/hash', 'verb' => 'GET'],
['name' => 'internal_bookmark#import_bookmark', 'url' => '/folder/{folder}/import', 'verb' => 'POST'],
['name' => 'internal_folders#undelete_folder', 'url' => '/folder/{folderId}/undelete', 'verb' => 'POST'],
['name' => 'internal_folders#get_folder_children', 'url' => '/folder/{folderId}/children', 'verb' => 'GET'],
['name' => 'internal_folders#get_folder_children_order', 'url' => '/folder/{folderId}/childorder', 'verb' => 'GET'],
['name' => 'internal_folders#set_folder_children_order', 'url' => '/folder/{folderId}/childorder', 'verb' => 'PATCH'],
['name' => 'internal_folders#add_to_folder', 'url' => '/folder/{folderId}/bookmarks/{bookmarkId}', 'verb' => 'POST'],
['name' => 'internal_folders#remove_from_folder', 'url' => '/folder/{folderId}/bookmarks/{bookmarkId}', 'verb' => 'DELETE'],
['name' => 'internal_folders#undelete_from_folder', 'url' => '/folder/{folderId}/bookmarks/{bookmarkId}/undelete', 'verb' => 'POST'],
['name' => 'internal_folders#get_folder_public_token', 'url' => '/folder/{folderId}/publictoken', 'verb' => 'GET'],
['name' => 'internal_folders#create_folder_public_token', 'url' => '/folder/{folderId}/publictoken', 'verb' => 'POST'],
['name' => 'internal_folders#delete_folder_public_token', 'url' => '/folder/{folderId}/publictoken', 'verb' => 'DELETE'],
Expand Down Expand Up @@ -110,12 +115,14 @@
['name' => 'folders#edit_folder', 'url' => '/public/rest/v2/folder/{folderId}', 'verb' => 'PUT'],
['name' => 'folders#delete_folder', 'url' => '/public/rest/v2/folder/{folderId}', 'verb' => 'DELETE'],
['name' => 'folders#hash_folder', 'url' => '/public/rest/v2/folder/{folderId}/hash', 'verb' => 'GET'],
['name' => 'folders#undelete_folder', 'url' => '/public/rest/v2/folder/{folderId}/undelete', 'verb' => 'POST'],
['name' => 'bookmark#import_bookmark', 'url' => '/public/rest/v2/folder/{folder}/import', 'verb' => 'POST'],
['name' => 'folders#get_folder_children', 'url' => '/public/rest/v2/folder/{folderId}/children', 'verb' => 'GET'],
['name' => 'folders#get_folder_children_order', 'url' => '/public/rest/v2/folder/{folderId}/childorder', 'verb' => 'GET'],
['name' => 'folders#set_folder_children_order', 'url' => '/public/rest/v2/folder/{folderId}/childorder', 'verb' => 'PATCH'],
['name' => 'folders#add_to_folder', 'url' => '/public/rest/v2/folder/{folderId}/bookmarks/{bookmarkId}', 'verb' => 'POST'],
['name' => 'folders#remove_from_folder', 'url' => '/public/rest/v2/folder/{folderId}/bookmarks/{bookmarkId}', 'verb' => 'DELETE'],
['name' => 'folders#undelete_from_folder', 'url' => '/public/rest/v2/folder/{folderId}/bookmarks/{bookmarkId}/undelete', 'verb' => 'POST'],
['name' => 'bookmark#preflighted_cors', 'url' => '/public/rest/v2/{path}',
'verb' => 'OPTIONS', 'requirements' => ['path' => '.+']],
['name' => 'folders#get_folder_public_token', 'url' => '/public/rest/v2/folder/{folderId}/publictoken', 'verb' => 'GET'],
Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"require-dev": {
"phpunit/phpunit": "^9.5.26",
"nextcloud/coding-standard": "^1.0.0",
"vimeo/psalm": "^4",
"vimeo/psalm": "5.x",
"nextcloud/ocp": "dev-master"
},
"config": {
Expand Down
11 changes: 7 additions & 4 deletions lib/Activity/ActivityPublisher.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
use OCA\Bookmarks\Db\SharedFolder;
use OCA\Bookmarks\Db\SharedFolderMapper;
use OCA\Bookmarks\Db\TreeMapper;
use OCA\Bookmarks\Events\BeforeDeleteEvent;
use OCA\Bookmarks\Events\BeforeSoftDeleteEvent;
use OCA\Bookmarks\Events\ChangeEvent;
use OCA\Bookmarks\Events\CreateEvent;
use OCA\Bookmarks\Events\MoveEvent;
Expand All @@ -26,6 +26,9 @@
use OCP\EventDispatcher\IEventListener;
use OCP\IL10N;

/**
* @psalm-implements IEventListener<ChangeEvent>
*/
class ActivityPublisher implements IEventListener {
/**
* @var IManager
Expand Down Expand Up @@ -117,7 +120,7 @@ public function publishShare(ChangeEvent $event): void {

if ($event instanceof CreateEvent) {
$activity->setSubject('share_created', ['folder' => $sharedFolder->getTitle(), 'sharee' => $sharedFolder->getUserId()]);
} elseif ($event instanceof BeforeDeleteEvent) {
} elseif ($event instanceof BeforeSoftDeleteEvent) {
$activity->setSubject('share_deleted', ['folder' => $sharedFolder->getTitle(), 'sharee' => $sharedFolder->getUserId()]);
} else {
return;
Expand Down Expand Up @@ -151,7 +154,7 @@ public function publishFolder(ChangeEvent $event): void {
$activity->setObject(TreeMapper::TYPE_FOLDER, $folder->getId());
if ($event instanceof CreateEvent) {
$activity->setSubject('folder_created', ['folder' => $folder->getTitle()]);
} elseif ($event instanceof BeforeDeleteEvent) {
} elseif ($event instanceof BeforeSoftDeleteEvent) {
$activity->setSubject('folder_deleted', ['folder' => $folder->getTitle()]);
} elseif ($event instanceof MoveEvent) {
$activity->setSubject('folder_moved', ['folder' => $folder->getTitle()]);
Expand Down Expand Up @@ -203,7 +206,7 @@ public function publishBookmark(ChangeEvent $event): void {

if ($event instanceof CreateEvent) {
$activity->setSubject('bookmark_created', ['bookmark' => $bookmark->getTitle()]);
} elseif ($event instanceof BeforeDeleteEvent) {
} elseif ($event instanceof BeforeSoftDeleteEvent) {
$activity->setSubject('bookmark_deleted', ['bookmark' => $bookmark->getTitle()]);
} else {
return;
Expand Down
4 changes: 4 additions & 0 deletions lib/AppInfo/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
use OCA\Bookmarks\Dashboard\Frequent;
use OCA\Bookmarks\Dashboard\Recent;
use OCA\Bookmarks\Events\BeforeDeleteEvent;
use OCA\Bookmarks\Events\BeforeSoftDeleteEvent;
use OCA\Bookmarks\Events\BeforeSoftUndeleteEvent;
use OCA\Bookmarks\Events\CreateEvent;
use OCA\Bookmarks\Events\MoveEvent;
use OCA\Bookmarks\Events\UpdateEvent;
Expand Down Expand Up @@ -79,6 +81,8 @@ public function register(IRegistrationContext $context): void {
$context->registerEventListener(UpdateEvent::class, TreeCacheManager::class);
$context->registerEventListener(BeforeDeleteEvent::class, TreeCacheManager::class);
$context->registerEventListener(MoveEvent::class, TreeCacheManager::class);
$context->registerEventListener(BeforeSoftDeleteEvent::class, TreeCacheManager::class);
$context->registerEventListener(BeforeSoftUndeleteEvent::class, TreeCacheManager::class);

$context->registerEventListener(CreateEvent::class, ActivityPublisher::class);
$context->registerEventListener(UpdateEvent::class, ActivityPublisher::class);
Expand Down
7 changes: 6 additions & 1 deletion lib/AugmentedTemplateResponse.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,15 @@
use OC;
use OCP\AppFramework\Http\TemplateResponse;

/**
* @psalm-template S of int
* @psalm-template H of array<string, mixed>
* @psalm-implements TemplateResponse<S,H>
*/
class AugmentedTemplateResponse extends TemplateResponse {
public function render() {
$return = parent::render();
$return = preg_replace('/<link rel="manifest" href="(.*?)">/i', '<link rel="manifest" href="'. OC::$server->getURLGenerator()->linkToRouteAbsolute('bookmarks.web_view.manifest').'">', $return);
preg_replace('/<link rel="manifest" href="(.*?)">/i', '<link rel="manifest" href="'. OC::$server->getURLGenerator()->linkToRouteAbsolute('bookmarks.web_view.manifest').'">', $return);
return $return;
}
}
33 changes: 33 additions & 0 deletions lib/BackgroundJobs/EmptyTrashbinJob.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php
/*
* Copyright (c) 2020-2024. The Nextcloud Bookmarks contributors.
*
* This file is licensed under the Affero General Public License version 3 or later. See the COPYING file.
*/

namespace OCA\Bookmarks\BackgroundJobs;

use OCA\Bookmarks\Db\TreeMapper;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\BackgroundJob\TimedJob;

class EmptyTrashbinJob extends TimedJob {
public const BATCH_SIZE = 1000; // 40 items
public const INTERVAL = 5 * 60; // 5 minutes
public const TRASHBIN_TTL = 2 * 4 * 4 * 7 * 24 * 60 * 60; // Two months


public function __construct(
ITimeFactory $timeFactory,
private TreeMapper $treeMapper,
) {
parent::__construct($timeFactory);

$this->setInterval(self::INTERVAL);
$this->setTimeSensitivity(self::TIME_INSENSITIVE);
}

protected function run($argument) {
$this->treeMapper->deleteOldTrashbinItems(self::BATCH_SIZE, self::TRASHBIN_TTL);
}
}
34 changes: 32 additions & 2 deletions lib/Controller/BookmarkController.php
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ public function __construct(
*
* @return ((int|mixed)[]|mixed)[]
*
* @psalm-return array{folders: array<array-key, int>, tags: array|mixed}
* @psalm-return array{folders: array<array-key, int>, tags: array<array-key, mixed>|mixed, archivedFilePath?: mixed|string, archivedFileType?: mixed|string, ...<array-key, mixed>}
*/
private function _returnBookmarkAsArray(Bookmark $bookmark): array {
$array = $bookmark->toArray();
Expand Down Expand Up @@ -265,6 +265,8 @@ public function getSingleBookmark($id): JSONResponse {
* @param bool|null $unavailable
* @param bool|null $archived
* @param bool|null $duplicated
* @param bool|null $recursive
* @param bool|null $deleted
* @return DataResponse
*
* @NoAdminRequired
Expand All @@ -286,6 +288,7 @@ public function getBookmarks(
?bool $archived = null,
?bool $duplicated = null,
bool $recursive = false,
bool $deleted = false,
): DataResponse {
$this->registerResponder('rss', function (DataResponse $res) {
if ($res->getData()['status'] === 'success') {
Expand All @@ -302,7 +305,9 @@ public function getBookmarks(
'description' => $description,
'bookmarks' => $bookmarks,
], '');
$response->setHeaders($res->getHeaders());
/** @var array<string, mixed> $headers */
$headers = $res->getHeaders();
$response->setHeaders($headers);
$response->setStatus($res->getStatus());
if (stripos($this->request->getHeader('accept'), 'application/rss+xml') !== false) {
$response->addHeader('Content-Type', 'application/rss+xml');
Expand Down Expand Up @@ -343,6 +348,10 @@ public function getBookmarks(
if ($duplicated !== null) {
$params->setDuplicated($duplicated);
}
// search soft deleted bookmarks
$params->setSoftDeleted($deleted);
// search bookmarks only in soft-deleted folders
$params->setSoftDeletedFolders($deleted);
$params->setTags($filterTag);
$params->setSearch($search);
$params->setConjunction($conjunction);
Expand Down Expand Up @@ -852,4 +861,25 @@ public function releaseLock(): JSONResponse {
}
return new JSONResponse(['status' => 'success']);
}

/**
* @return Http\DataResponse
* @NoAdminRequired
* @NoCSRFRequired
*
* @PublicPage
*/
public function getDeletedBookmarks(): DataResponse {
$this->authorizer->setCredentials($this->request);
if ($this->authorizer->getUserId() === null) {
return new Http\DataResponse(['status' => 'error', 'data' => 'Unauthorized'], Http::STATUS_FORBIDDEN);
}
try {
$bookmarks = $this->treeMapper->getSoftDeletedRootItems($this->authorizer->getUserId(), TreeMapper::TYPE_BOOKMARK);
} catch (UrlParseError|\OCP\DB\Exception $e) {
return new Http\DataResponse(['status' => 'error', 'data' => 'Internal error'], Http::STATUS_INTERNAL_SERVER_ERROR);
}

return new Http\DataResponse(['status' => 'success', 'data' => array_map(fn ($bookmark) => $this->_returnBookmarkAsArray($bookmark), $bookmarks)]);
}
}
Loading
Loading