Skip to content

Commit

Permalink
fix(notifications): Preparse call notifications for improved performance
Browse files Browse the repository at this point in the history
Signed-off-by: Joas Schilling <coding@schilljs.com>
  • Loading branch information
nickvergessen committed Apr 30, 2024
1 parent 59a3b52 commit 33ad5d2
Show file tree
Hide file tree
Showing 2 changed files with 58 additions and 14 deletions.
41 changes: 38 additions & 3 deletions lib/Notification/Listener.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,12 @@
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\EventDispatcher\IEventListener;
use OCP\IConfig;
use OCP\IDBConnection;
use OCP\IUser;
use OCP\IUserSession;
use OCP\Notification\IManager;
use OCP\Notification\INotification;
use Psr\Log\LoggerInterface;

/**
Expand All @@ -53,10 +55,14 @@
class Listener implements IEventListener {

protected bool $shouldSendCallNotification = false;
/** @var array<string, INotification> $preparedCallNotifications Map of language => parsed notification in that language */
protected array $preparedCallNotifications = [];

public function __construct(
protected IConfig $serverConfig,
protected IDBConnection $connection,
protected IManager $notificationManager,
protected Notifier $notificationProvider,
protected ParticipantService $participantsService,
protected IEventDispatcher $dispatcher,
protected IUserSession $userSession,
Expand Down Expand Up @@ -305,18 +311,47 @@ protected function sendCallNotifications(Room $room): void {
if ($room->getToken() === 'c9bui2ju') {
\OC::$server->getLogger()->warning('Debugging step #7.1: ' . microtime(true));
}

$this->preparedCallNotifications = [];
$userIds = $this->participantsService->getParticipantUserIdsForCallNotifications($room);
if ($room->getToken() === 'c9bui2ju') {
\OC::$server->getLogger()->warning('Debugging step #7.2: ' . microtime(true));
// Room name depends on the notification user for one-to-one,
// so we avoid preparsing it there. Also, it comes with some base load,
// so we only do it for "big enough" calls.
$preparseNotificationForPush = count($userIds) > 10;
if ($preparseNotificationForPush) {
$fallbackLang = $this->serverConfig->getSystemValue('force_language', null);
if (is_string($fallbackLang)) {
/** @psalm-var array<string, string> $userLanguages */
$userLanguages = [];
} else {
$fallbackLang = $this->serverConfig->getSystemValueString('default_language', 'en');
/** @psalm-var array<string, string> $userLanguages */
$userLanguages = $this->serverConfig->getUserValueForUsers('core', 'lang', $userIds);
}
}

$this->connection->beginTransaction();
try {
foreach ($userIds as $userId) {
if ($actorId === $userId) {
continue;
}

if ($preparseNotificationForPush) {
// Get the settings for this particular user, then check if we have notifications to email them
$languageCode = $userLanguages[$userId] ?? $fallbackLang;

if (!isset($this->preparedCallNotifications[$languageCode])) {
$translatedNotification = clone $notification;

$this->notificationManager->setPreparingPushNotification(true);
$this->preparedCallNotifications[$languageCode] = $this->notificationProvider->prepare($translatedNotification, $languageCode);
$this->notificationManager->setPreparingPushNotification(false);
$notification = $translatedNotification;
} else {
$notification = $this->preparedCallNotifications[$languageCode];
}
}

try {
$notification->setUser($userId);
$this->notificationManager->notify($notification);
Expand Down
31 changes: 20 additions & 11 deletions lib/Notification/Notifier.php
Original file line number Diff line number Diff line change
Expand Up @@ -197,10 +197,12 @@ public function prepare(INotification $notification, string $languageCode): INot
throw new \InvalidArgumentException('Incorrect app');
}

$userId = $notification->getUser();
$user = $this->userManager->get($userId);
if (!$user instanceof IUser || $this->config->isDisabledForUser($user)) {
throw new AlreadyProcessedException();
if (!$this->notificationManager->isPreparingPushNotification() && $notification->getSubject() === 'call') {
$userId = $notification->getUser();
$user = $this->userManager->get($userId);
if (!$user instanceof IUser || $this->config->isDisabledForUser($user)) {
throw new AlreadyProcessedException();
}
}

$l = $this->lFactory->get(Application::APP_ID, $languageCode);
Expand All @@ -217,20 +219,27 @@ public function prepare(INotification $notification, string $languageCode): INot
return $this->parseCertificateExpiration($notification, $l);
}

try {
$room = $this->getRoom($notification->getObjectId(), $userId);
} catch (RoomNotFoundException $e) {
// Room does not exist
throw new AlreadyProcessedException();
}

if ($this->notificationManager->isPreparingPushNotification() && $notification->getSubject() === 'call') {
try {
$room = $this->manager->getRoomByToken($notification->getObjectId());
} catch (RoomNotFoundException) {
// Room does not exist
throw new AlreadyProcessedException();
}

// Skip the participant check when we generate push notifications
// we just looped over the participants to create the notification,
// they can not be removed between these 2 steps, but we can save
// n queries.
$participant = null;
} else {
try {
$room = $this->getRoom($notification->getObjectId(), $userId);
} catch (RoomNotFoundException $e) {
// Room does not exist
throw new AlreadyProcessedException();
}

try {
$participant = $this->getParticipant($room, $userId);
} catch (ParticipantNotFoundException $e) {
Expand Down

0 comments on commit 33ad5d2

Please sign in to comment.