Skip to content

Commit dba756c

Browse files
committed
Enhancement: Make tables import asynchronous
Signed-off-by: Kostiantyn Miakshyn <molodchick@gmail.com>
1 parent 318cdae commit dba756c

File tree

12 files changed

+504
-76
lines changed

12 files changed

+504
-76
lines changed

appinfo/info.xml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,17 @@ Have a good time and manage whatever you want.
5050
<database>sqlite</database>
5151
<nextcloud min-version="29" max-version="32"/>
5252
</dependencies>
53+
<activity>
54+
<settings>
55+
<setting>OCA\Tables\Activity\Setting</setting>
56+
</settings>
57+
<filters>
58+
<filter>OCA\Tables\Activity\Filter</filter>
59+
</filters>
60+
<providers>
61+
<provider>OCA\Tables\Activity\Provider</provider>
62+
</providers>
63+
</activity>
5364
<repair-steps>
5465
<pre-migration>
5566
<step>OCA\Tables\Migration\FixContextsDefaults</step>

lib/Activity/ActivityConstants.php

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
/**
4+
* SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors
5+
* SPDX-License-Identifier: AGPL-3.0-or-later
6+
*/
7+
8+
namespace OCA\Tables\Activity;
9+
10+
class ActivityConstants {
11+
public const APP_ID = 'tables';
12+
13+
/*****
14+
* Types can have different Settings for Mail/Notifications.
15+
*/
16+
public const TYPE_IMPORT_FINISHED = 'tables_import_finished';
17+
18+
/*****
19+
* Subjects are internal 'types', that get interpreted by our own Provider.
20+
*/
21+
22+
/**
23+
* Somebody shared a form to a selected user
24+
* Needs Params:
25+
* "user": The userId of the user who shared.
26+
* "formTitle": The hash of the shared form.
27+
* "formHash": The hash of the shared form
28+
*/
29+
public const SUBJECT_IMPORT_FINISHED = 'import_finished_subject';
30+
31+
public const MESSAGE_IMPORT_FINISHED = 'import_finished_message';
32+
}

lib/Activity/ActivityManager.php

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?php
2+
3+
/**
4+
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
5+
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
6+
* SPDX-License-Identifier: AGPL-3.0-only
7+
*/
8+
namespace OCA\Tables\Activity;
9+
10+
use OCP\Activity\IManager;
11+
use OCP\App\IAppManager;
12+
use OCP\Files\Config\IMountProviderCollection;
13+
use OCP\Files\IRootFolder;
14+
use OCP\IUserSession;
15+
use OCP\Share\IShareHelper;
16+
17+
class ActivityManager {
18+
public function __construct(
19+
protected IManager $activityManager,
20+
protected IUserSession $session,
21+
protected IAppManager $appManager,
22+
protected IMountProviderCollection $mountCollection,
23+
protected IRootFolder $rootFolder,
24+
protected IShareHelper $shareHelper,
25+
) {
26+
}
27+
28+
public function notifyImportFinished(string $userId): void {
29+
30+
$activity = $this->activityManager->generateEvent();
31+
$activity->setApp(ActivityConstants::APP_ID)
32+
->setType(ActivityConstants::TYPE_IMPORT_FINISHED)
33+
->setAuthor($userId)
34+
->setObject($event->getComment()->getObjectType(), (int)$event->getComment()->getObjectId())
35+
->setMessage(ActivityConstants::MESSAGE_IMPORT_FINISHED, [
36+
'commentId' => $event->getComment()->getId(),
37+
]);
38+
39+
$activity->setAffectedUser($userId);
40+
$activity->setSubject(ActivityConstants::SUBJECT_IMPORT_FINISHED, [
41+
'actor' => $userId,
42+
'fileId' => (int)$event->getComment()->getObjectId(),
43+
'filePath' => trim($path, '/'),
44+
]);
45+
$this->activityManager->publish($activity);
46+
}
47+
}

lib/Activity/Filter.php

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?php
2+
3+
/**
4+
* SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
5+
* SPDX-License-Identifier: AGPL-3.0-or-later
6+
*/
7+
namespace OCA\Tables\Activity;
8+
9+
use OCP\Activity\IFilter;
10+
use OCP\IL10N;
11+
use OCP\IURLGenerator;
12+
13+
class Filter implements IFilter {
14+
public function __construct(
15+
protected IL10N $l,
16+
protected IURLGenerator $url,
17+
) {
18+
}
19+
20+
public function getIdentifier(): string {
21+
return ActivityConstants::APP_ID;
22+
}
23+
24+
public function getName(): string {
25+
return $this->l->t('Tables');
26+
}
27+
28+
public function getPriority(): int {
29+
return 40;
30+
}
31+
32+
public function getIcon(): string {
33+
return $this->url->getAbsoluteURL($this->url->imagePath(ActivityConstants::APP_ID, 'app-dark.svg'));
34+
}
35+
36+
public function filterTypes(array $types): array {
37+
return $types;
38+
}
39+
40+
public function allowedApps(): array {
41+
return [ActivityConstants::APP_ID];
42+
}
43+
}

lib/Activity/Provider.php

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
<?php
2+
3+
/**
4+
* SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
5+
* SPDX-License-Identifier: AGPL-3.0-or-later
6+
*/
7+
namespace OCA\Tables\Activity;
8+
9+
use OCP\Activity\Exceptions\UnknownActivityException;
10+
use OCP\Activity\IEvent;
11+
use OCP\Activity\IManager;
12+
use OCP\Activity\IProvider;
13+
use OCP\Comments\ICommentsManager;
14+
use OCP\Comments\NotFoundException;
15+
use OCP\IL10N;
16+
use OCP\IURLGenerator;
17+
use OCP\IUserManager;
18+
use OCP\L10N\IFactory;
19+
20+
class Provider implements IProvider {
21+
protected ?IL10N $l = null;
22+
23+
public function __construct(
24+
protected IFactory $languageFactory,
25+
protected IURLGenerator $url,
26+
protected ICommentsManager $commentsManager,
27+
protected IUserManager $userManager,
28+
protected IManager $activityManager,
29+
) {
30+
}
31+
32+
/**
33+
* @param string $language
34+
* @param IEvent $event
35+
* @param IEvent|null $previousEvent
36+
* @return IEvent
37+
* @throws UnknownActivityException
38+
* @since 11.0.0
39+
*/
40+
public function parse($language, IEvent $event, ?IEvent $previousEvent = null): IEvent {
41+
if ($event->getApp() !== ActivityConstants::APP_ID) {
42+
throw new UnknownActivityException();
43+
}
44+
45+
$this->l = $this->languageFactory->get(ActivityConstants::APP_ID, $language);
46+
47+
if ($event->getSubject() === ActivityConstants::SUBJECT_IMPORT_FINISHED) {
48+
$this->parseMessage($event);
49+
if ($this->activityManager->getRequirePNG()) {
50+
$event->setIcon($this->url->getAbsoluteURL($this->url->imagePath('core', 'actions/comment.png')));
51+
} else {
52+
$event->setIcon($this->url->getAbsoluteURL($this->url->imagePath('core', 'actions/comment.svg')));
53+
}
54+
55+
if ($this->activityManager->isFormattingFilteredObject()) {
56+
try {
57+
return $this->parseShortVersion($event);
58+
} catch (UnknownActivityException) {
59+
// Ignore and simply use the long version...
60+
}
61+
}
62+
63+
return $this->parseLongVersion($event);
64+
}
65+
throw new UnknownActivityException();
66+
67+
}
68+
69+
/**
70+
* @throws UnknownActivityException
71+
*/
72+
protected function parseShortVersion(IEvent $event): IEvent {
73+
$subjectParameters = $this->getSubjectParameters($event);
74+
75+
if ($event->getSubject() === 'add_comment_subject') {
76+
if ($subjectParameters['actor'] === $this->activityManager->getCurrentUserId()) {
77+
$event->setRichSubject($this->l->t('You commented'), []);
78+
} else {
79+
$author = $this->generateUserParameter($subjectParameters['actor']);
80+
$event->setRichSubject($this->l->t('{author} commented'), [
81+
'author' => $author,
82+
]);
83+
}
84+
} else {
85+
throw new UnknownActivityException();
86+
}
87+
88+
return $event;
89+
}
90+
91+
/**
92+
* @throws UnknownActivityException
93+
*/
94+
protected function parseLongVersion(IEvent $event): IEvent {
95+
$subjectParameters = $this->getSubjectParameters($event);
96+
97+
if ($event->getSubject() === 'add_comment_subject') {
98+
if ($subjectParameters['actor'] === $this->activityManager->getCurrentUserId()) {
99+
$event->setParsedSubject($this->l->t('You commented on %1$s', [
100+
$subjectParameters['filePath'],
101+
]))
102+
->setRichSubject($this->l->t('You commented on {file}'), [
103+
'file' => $this->generateFileParameter($subjectParameters['fileId'], $subjectParameters['filePath']),
104+
]);
105+
} else {
106+
$author = $this->generateUserParameter($subjectParameters['actor']);
107+
$event->setParsedSubject($this->l->t('%1$s commented on %2$s', [
108+
$author['name'],
109+
$subjectParameters['filePath'],
110+
]))
111+
->setRichSubject($this->l->t('{author} commented on {file}'), [
112+
'author' => $author,
113+
'file' => $this->generateFileParameter($subjectParameters['fileId'], $subjectParameters['filePath']),
114+
]);
115+
}
116+
} else {
117+
throw new UnknownActivityException();
118+
}
119+
120+
return $event;
121+
}
122+
123+
protected function getSubjectParameters(IEvent $event): array {
124+
$subjectParameters = $event->getSubjectParameters();
125+
if (isset($subjectParameters['fileId'])) {
126+
return $subjectParameters;
127+
}
128+
129+
// Fix subjects from 12.0.3 and older
130+
//
131+
// Do NOT Remove unless necessary
132+
// Removing this will break parsing of activities that were created on
133+
// Nextcloud 12, so we should keep this as long as it's acceptable.
134+
// Otherwise if people upgrade over multiple releases in a short period,
135+
// they will get the dead entries in their stream.
136+
return [
137+
'actor' => $subjectParameters[0],
138+
'fileId' => $event->getObjectId(),
139+
'filePath' => trim($subjectParameters[1], '/'),
140+
];
141+
}
142+
143+
protected function parseMessage(IEvent $event): void {
144+
$messageParameters = $event->getMessageParameters();
145+
if (empty($messageParameters)) {
146+
// Email
147+
return;
148+
}
149+
150+
$commentId = $messageParameters['commentId'] ?? $messageParameters[0];
151+
152+
try {
153+
$comment = $this->commentsManager->get((string)$commentId);
154+
$message = $comment->getMessage();
155+
156+
$mentionCount = 1;
157+
$mentions = [];
158+
foreach ($comment->getMentions() as $mention) {
159+
if ($mention['type'] !== 'user') {
160+
continue;
161+
}
162+
163+
$message = str_replace('@"' . $mention['id'] . '"', '{mention' . $mentionCount . '}', $message);
164+
if (!str_contains($mention['id'], ' ') && !str_starts_with($mention['id'], 'guest/')) {
165+
$message = str_replace('@' . $mention['id'], '{mention' . $mentionCount . '}', $message);
166+
}
167+
168+
$mentions['mention' . $mentionCount] = $this->generateUserParameter($mention['id']);
169+
$mentionCount++;
170+
}
171+
172+
$event->setParsedMessage($comment->getMessage())
173+
->setRichMessage($message, $mentions);
174+
} catch (NotFoundException $e) {
175+
}
176+
}
177+
178+
/**
179+
* @return array<string, string>
180+
*/
181+
protected function generateFileParameter(int $id, string $path): array {
182+
return [
183+
'type' => 'file',
184+
'id' => (string)$id,
185+
'name' => basename($path),
186+
'path' => $path,
187+
'link' => $this->url->linkToRouteAbsolute('files.viewcontroller.showFile', ['fileid' => $id]),
188+
];
189+
}
190+
191+
protected function generateUserParameter(string $uid): array {
192+
return [
193+
'type' => 'user',
194+
'id' => $uid,
195+
'name' => $this->userManager->getDisplayName($uid) ?? $uid,
196+
];
197+
}
198+
}

lib/Activity/Setting.php

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<?php
2+
3+
/**
4+
* SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
5+
* SPDX-License-Identifier: AGPL-3.0-or-later
6+
*/
7+
namespace OCA\Tables\Activity;
8+
9+
use OCP\Activity\ActivitySettings;
10+
use OCP\IL10N;
11+
12+
class Setting extends ActivitySettings {
13+
public function __construct(
14+
protected IL10N $l,
15+
) {
16+
}
17+
18+
public function getIdentifier(): string {
19+
return ActivityConstants::TYPE_IMPORT_FINISHED;
20+
}
21+
22+
public function getName(): string {
23+
return $this->l->t('<strong>Import</strong> of a file has finished');
24+
}
25+
26+
public function getGroupIdentifier() {
27+
return ActivityConstants::APP_ID;
28+
}
29+
30+
public function getGroupName() {
31+
return $this->l->t('Tables');
32+
}
33+
34+
public function getPriority(): int {
35+
return 50;
36+
}
37+
38+
public function canChangeStream(): bool {
39+
return true;
40+
}
41+
42+
public function isDefaultEnabledStream(): bool {
43+
return true;
44+
}
45+
46+
public function canChangeMail(): bool {
47+
return true;
48+
}
49+
50+
public function isDefaultEnabledMail(): bool {
51+
return false;
52+
}
53+
}

0 commit comments

Comments
 (0)