diff options
author | Joas Schilling <213943+nickvergessen@users.noreply.github.com> | 2023-01-24 16:35:00 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-01-24 16:35:00 +0100 |
commit | bfee410ec653a1022e78fc8ce660299ba4529dcf (patch) | |
tree | 550b32a53c826cd0182908244de3630b98056eb3 | |
parent | bc863268b420341c0930f65f2c32f91e197ecf8a (diff) | |
parent | cca8617cd70298e205aa3a4f68dc174d73009f15 (diff) |
Merge pull request #8550 from nextcloud/feature/notify-recording-stored
Notify recording stored
-rw-r--r-- | appinfo/routes/routesRecordingController.php | 4 | ||||
-rw-r--r-- | docs/recording.md | 37 | ||||
-rw-r--r-- | lib/Chat/SystemMessage/Listener.php | 4 | ||||
-rw-r--r-- | lib/Controller/RecordingController.php | 35 | ||||
-rw-r--r-- | lib/Notification/Notifier.php | 70 | ||||
-rw-r--r-- | lib/Service/RecordingService.php | 115 | ||||
-rw-r--r-- | tests/integration/features/callapi/recording.feature | 3 | ||||
-rw-r--r-- | tests/php/Notification/NotifierTest.php | 9 | ||||
-rw-r--r-- | tests/php/Service/RecordingServiceTest.php | 32 |
9 files changed, 301 insertions, 8 deletions
diff --git a/appinfo/routes/routesRecordingController.php b/appinfo/routes/routesRecordingController.php index 4e9dc7d4a..ef1facc12 100644 --- a/appinfo/routes/routesRecordingController.php +++ b/appinfo/routes/routesRecordingController.php @@ -36,5 +36,9 @@ return [ ['name' => 'Recording#stop', 'url' => '/api/{apiVersion}/recording/{token}', 'verb' => 'DELETE', 'requirements' => $requirements], /** @see \OCA\Talk\Controller\RecordingController::store() */ ['name' => 'Recording#store', 'url' => '/api/{apiVersion}/recording/{token}/store', 'verb' => 'POST', 'requirements' => $requirements], + /** @see \OCA\Talk\Controller\RecordingController::notificationDismiss() */ + ['name' => 'Recording#notificationDismiss', 'url' => '/api/{apiVersion}/recording/{token}/notification', 'verb' => 'DELETE', 'requirements' => $requirements], + /** @see \OCA\Talk\Controller\RecordingController::shareToChat() */ + ['name' => 'Recording#shareToChat', 'url' => '/api/{apiVersion}/recording/{token}/share-chat', 'verb' => 'POST', 'requirements' => $requirements], ], ]; diff --git a/docs/recording.md b/docs/recording.md index a73feb8a8..b0cb04e56 100644 --- a/docs/recording.md +++ b/docs/recording.md @@ -71,3 +71,40 @@ + `401 Unauthorized` When the validation as SIP bridge failed + `404 Not Found` Room not found + `429 Too Many Request` Brute force protection + +## Dismiss store call recording notification + +* Required capability: `recording-v1` +* Method: `DELETE` +* Endpoint: `/recording/{token}/notification` +* Data: + +| field | type | Description | +| ----------- | ------ | --------------------------------------------------------------------- | +| `timestamp` | string | Timestamp in seconds and UTC time zone that notification was created. | + +* Response: + - Status code: + + `200 OK` + + `403 Forbidden` When the user is not a moderator/owner. + + `404 Not Found` Room not found + +## Share store call recording + +* Required capability: `recording-v1` +* Method: `POST` +* Endpoint: `/recording/{token}/share-chat` +* Data: + +| field | type | Description | +| ----------- | ------- | --------------------------------------------------------------------- | +| `timestamp` | string | Timestamp in seconds and UTC time zone that notification was created. | +| `fileId` | integer | File id of recording to share at the room. | + +* Response: + - Status code: + + `200 OK` + + `400 Bad Request` Error: `file`: Shared file is invalid + + `400 Bad Request` Error: `system`: Internal system error + + `403 Forbidden` When the user is not a moderator/owne + + `404 Not Found` Room not found diff --git a/lib/Chat/SystemMessage/Listener.php b/lib/Chat/SystemMessage/Listener.php index fecc384eb..9e33a8aca 100644 --- a/lib/Chat/SystemMessage/Listener.php +++ b/lib/Chat/SystemMessage/Listener.php @@ -382,6 +382,10 @@ class Listener implements IEventListener { $listener = Server::get(self::class); $manager = Server::get(Manager::class); + $request = Server::get(IRequest::class); + if ($request->getParam('_route') === 'ocs.spreed.Recording.shareToChat') { + return; + } $room = $manager->getRoomByToken($share->getSharedWith()); $metaData = Server::get(IRequest::class)->getParam('talkMetaData') ?? ''; $metaData = json_decode($metaData, true); diff --git a/lib/Controller/RecordingController.php b/lib/Controller/RecordingController.php index 0948fe538..d5ed53fa0 100644 --- a/lib/Controller/RecordingController.php +++ b/lib/Controller/RecordingController.php @@ -100,4 +100,39 @@ class RecordingController extends AEnvironmentAwareController { } return new DataResponse(); } + + /** + * @NoAdminRequired + * @RequireModeratorParticipant + */ + public function notificationDismiss(int $timestamp): DataResponse { + try { + $this->recordingService->notificationDismiss( + $this->getRoom(), + $this->participant, + $timestamp + ); + } catch (InvalidArgumentException $e) { + return new DataResponse(['error' => $e->getMessage()], Http::STATUS_BAD_REQUEST); + } + return new DataResponse(); + } + + /** + * @NoAdminRequired + * @RequireModeratorParticipant + */ + public function shareToChat(int $fileId, int $timestamp): DataResponse { + try { + $this->recordingService->shareToChat( + $this->getRoom(), + $this->participant, + $fileId, + $timestamp + ); + } catch (InvalidArgumentException $e) { + return new DataResponse(['error' => $e->getMessage()], Http::STATUS_BAD_REQUEST); + } + return new DataResponse(); + } } diff --git a/lib/Notification/Notifier.php b/lib/Notification/Notifier.php index 46c6b7b77..b9e65147e 100644 --- a/lib/Notification/Notifier.php +++ b/lib/Notification/Notifier.php @@ -41,6 +41,7 @@ use OCA\Talk\Webinary; use OCP\AppFramework\Utility\ITimeFactory; use OCP\Comments\ICommentsManager; use OCP\Comments\NotFoundException; +use OCP\Files\IRootFolder; use OCP\HintException; use OCP\IL10N; use OCP\IURLGenerator; @@ -69,6 +70,8 @@ class Notifier implements INotifier { protected INotificationManager $notificationManager; protected ICommentsManager $commentManager; protected MessageParser $messageParser; + protected IURLGenerator $urlGenerator; + protected IRootFolder $rootFolder; protected ITimeFactory $timeFactory; protected Definitions $definitions; protected AddressHandler $addressHandler; @@ -89,6 +92,8 @@ class Notifier implements INotifier { INotificationManager $notificationManager, CommentsManager $commentManager, MessageParser $messageParser, + IURLGenerator $urlGenerator, + IRootFolder $rootFolder, ITimeFactory $timeFactory, Definitions $definitions, AddressHandler $addressHandler) { @@ -103,6 +108,8 @@ class Notifier implements INotifier { $this->notificationManager = $notificationManager; $this->commentManager = $commentManager; $this->messageParser = $messageParser; + $this->urlGenerator = $urlGenerator; + $this->rootFolder = $rootFolder; $this->timeFactory = $timeFactory; $this->definitions = $definitions; $this->addressHandler = $addressHandler; @@ -248,6 +255,9 @@ class Notifier implements INotifier { ->setLink($this->url->linkToRouteAbsolute('spreed.Page.showCall', ['token' => $room->getToken()])); $subject = $notification->getSubject(); + if ($subject === 'record_file_stored') { + return $this->parseStoredRecording($notification, $room, $participant, $l); + } if ($subject === 'invitation') { return $this->parseInvitation($notification, $room, $l); } @@ -287,6 +297,66 @@ class Notifier implements INotifier { return $temp; } + private function parseStoredRecording( + INotification $notification, + Room $room, + Participant $participant, + IL10N $l + ): INotification { + $parameters = $notification->getSubjectParameters(); + try { + $userFolder = $this->rootFolder->getUserFolder($notification->getUser()); + /** @var \OCP\Files\File[] */ + $files = $userFolder->getById($parameters['objectId']); + $file = array_shift($files); + } catch (\Throwable $th) { + throw new AlreadyProcessedException(); + } + $shareAction = $notification->createAction() + ->setParsedLabel($l->t('Share to chat')) + ->setPrimary(true) + ->setLink( + $this->urlGenerator->linkToOCSRouteAbsolute( + 'spreed.Recording.shareToChat', + [ + 'apiVersion' => 'v1', + 'fileId' => $file->getId(), + 'timestamp' => $notification->getDateTime()->getTimestamp(), + 'token' => $room->getToken() + ] + ), + IAction::TYPE_POST + ); + $dismissAction = $notification->createAction() + ->setParsedLabel($l->t('Dismiss notification')) + ->setLink( + $this->urlGenerator->linkToOCSRouteAbsolute( + 'spreed.Recording.notificationDismiss', + [ + 'apiVersion' => 'v1', + 'token' => $room->getToken(), + 'timestamp' => $notification->getDateTime()->getTimestamp(), + ] + ), + IAction::TYPE_DELETE + ); + + $notification + ->setRichSubject( + $l->t('Recording for the call in {call} was uploaded.'), + [ + 'call' => [ + 'type' => 'call', + 'id' => $room->getId(), + 'name' => $room->getDisplayName($participant->getAttendee()->getActorId()), + 'call-type' => $this->getRoomType($room), + ], + ]) + ->addParsedAction($shareAction) + ->addParsedAction($dismissAction); + return $notification; + } + /** * @throws HintException */ diff --git a/lib/Service/RecordingService.php b/lib/Service/RecordingService.php index 408ebf066..2eaafb999 100644 --- a/lib/Service/RecordingService.php +++ b/lib/Service/RecordingService.php @@ -27,14 +27,23 @@ namespace OCA\Talk\Service; use InvalidArgumentException; use OC\User\NoUserException; +use OCA\Talk\Chat\ChatManager; use OCA\Talk\Config; use OCA\Talk\Exceptions\ParticipantNotFoundException; +use OCA\Talk\Manager; +use OCA\Talk\Participant; use OCA\Talk\Room; +use OCP\AppFramework\Utility\ITimeFactory; +use OCP\Files\File; use OCP\Files\Folder; use OCP\Files\IMimeTypeDetector; use OCP\Files\IRootFolder; use OCP\Files\NotFoundException; use OCP\Files\NotPermittedException; +use OCP\Notification\IManager; +use OCP\Share\IManager as ShareManager; +use OCP\Share\IShare; +use Psr\Log\LoggerInterface; class RecordingService { public const DEFAULT_ALLOWED_RECORDING_FORMATS = [ @@ -44,11 +53,17 @@ class RecordingService { ]; public function __construct( - private IMimeTypeDetector $mimeTypeDetector, - private ParticipantService $participantService, - private IRootFolder $rootFolder, - private Config $config, - private RoomService $roomService + protected IMimeTypeDetector $mimeTypeDetector, + protected ParticipantService $participantService, + protected IRootFolder $rootFolder, + protected IManager $notificationManager, + protected Manager $roomManager, + protected ITimeFactory $timeFactory, + protected Config $config, + protected RoomService $roomService, + protected ShareManager $shareManager, + protected ChatManager $chatManager, + protected LoggerInterface $logger, ) { } @@ -80,14 +95,15 @@ class RecordingService { $this->validateFileFormat($fileName, $content); try { - $this->participantService->getParticipant($room, $owner); + $participant = $this->participantService->getParticipant($room, $owner); } catch (ParticipantNotFoundException $e) { throw new InvalidArgumentException('owner_participant'); } try { $recordingFolder = $this->getRecordingFolder($owner, $room->getToken()); - $recordingFolder->newFile($fileName, $content); + $file = $recordingFolder->newFile($fileName, $content); + $this->notifyStoredRecording($room, $participant, $file); } catch (NoUserException $e) { throw new InvalidArgumentException('owner_invalid'); } catch (NotPermittedException $e) { @@ -142,4 +158,89 @@ class RecordingService { } return $recordingFolder; } + + public function notifyStoredRecording(Room $room, Participant $participant, File $file): void { + $attendee = $participant->getAttendee(); + + $notification = $this->notificationManager->createNotification(); + + $notification + ->setApp('spreed') + ->setDateTime($this->timeFactory->getDateTime()) + ->setObject('chat', $room->getToken()) + ->setUser($attendee->getActorId()) + ->setSubject('record_file_stored', [ + 'objectId' => $file->getId(), + ]); + $this->notificationManager->notify($notification); + } + + public function notificationDismiss(Room $room, Participant $participant, int $timestamp): void { + $notification = $this->notificationManager->createNotification(); + $notification->setApp('spreed') + ->setObject('chat', $room->getToken()) + ->setSubject('record_file_stored') + ->setDateTime($this->timeFactory->getDateTime('@' . $timestamp)) + ->setUser($participant->getAttendee()->getActorId()); + $this->notificationManager->markProcessed($notification); + } + + private function getTypeOfShare(string $mimetype): string { + if (str_starts_with($mimetype, 'video/')) { + return 'record-video'; + } + return 'record-audio'; + } + + public function shareToChat(Room $room, Participant $participant, int $fileId, int $timestamp): void { + try { + $userFolder = $this->rootFolder->getUserFolder( + $participant->getAttendee()->getActorId() + ); + /** @var \OCP\Files\File[] */ + $files = $userFolder->getById($fileId); + $file = array_shift($files); + } catch (\Throwable $th) { + throw new InvalidArgumentException('file'); + } + + $creationDateTime = $this->timeFactory->getDateTime(); + + $share = $this->shareManager->newShare(); + $share->setNodeId($fileId) + ->setShareTime($creationDateTime) + ->setSharedBy($participant->getAttendee()->getActorId()) + ->setNode($file) + ->setShareType(IShare::TYPE_ROOM) + ->setSharedWith($room->getToken()) + ->setPermissions(\OCP\Constants::PERMISSION_READ); + + $share = $this->shareManager->createShare($share); + + $message = json_encode([ + 'message' => 'file_shared', + 'parameters' => [ + 'share' => $share->getId(), + 'metaData' => [ + 'mimeType' => $file->getMimeType(), + 'messageType' => $this->getTypeOfShare($file->getMimeType()), + ], + ], + ]); + + try { + $this->chatManager->addSystemMessage( + $room, + $participant->getAttendee()->getActorType(), + $participant->getAttendee()->getActorId(), + $message, + $creationDateTime, + true + ); + } catch (\Exception $e) { + $this->logger->error($e->getMessage(), ['exception' => $e]); + throw new InvalidArgumentException('system'); + } + $this->notificationDismiss($room, $participant, $timestamp); + } } diff --git a/tests/integration/features/callapi/recording.feature b/tests/integration/features/callapi/recording.feature index 37d6cf326..5b4919723 100644 --- a/tests/integration/features/callapi/recording.feature +++ b/tests/integration/features/callapi/recording.feature @@ -121,3 +121,6 @@ Feature: callapi/recording | roomName | room1 | And user "participant1" joins room "room1" with 200 (v4) Then user "participant1" store recording file "/img/join_call.ogg" in room "room1" with 200 (v1) + And user "participant1" has the following notifications + | app | object_type | object_id | subject | + | spreed | chat | room1 | Recording for the call in room1 was uploaded. | diff --git a/tests/php/Notification/NotifierTest.php b/tests/php/Notification/NotifierTest.php index 6ec9a77c6..7c1b29419 100644 --- a/tests/php/Notification/NotifierTest.php +++ b/tests/php/Notification/NotifierTest.php @@ -37,6 +37,7 @@ use OCA\Talk\Room; use OCA\Talk\Service\ParticipantService; use OCP\AppFramework\Utility\ITimeFactory; use OCP\Comments\IComment; +use OCP\Files\IRootFolder; use OCP\IL10N; use OCP\IURLGenerator; use OCP\IUser; @@ -73,6 +74,10 @@ class NotifierTest extends TestCase { protected $commentsManager; /** @var MessageParser|MockObject */ protected $messageParser; + /** @var IURLGenerator|MockObject */ + protected $urlGenerator; + /** @var IRootFolder|MockObject */ + protected $rootFolder; /** @var ITimeFactory|MockObject */ protected $timeFactory; /** @var Definitions|MockObject */ @@ -95,6 +100,8 @@ class NotifierTest extends TestCase { $this->notificationManager = $this->createMock(INotificationManager::class); $this->commentsManager = $this->createMock(CommentsManager::class); $this->messageParser = $this->createMock(MessageParser::class); + $this->urlGenerator = $this->createMock(IURLGenerator::class); + $this->rootFolder = $this->createMock(IRootFolder::class); $this->timeFactory = $this->createMock(ITimeFactory::class); $this->definitions = $this->createMock(Definitions::class); $this->addressHandler = $this->createMock(AddressHandler::class); @@ -111,6 +118,8 @@ class NotifierTest extends TestCase { $this->notificationManager, $this->commentsManager, $this->messageParser, + $this->urlGenerator, + $this->rootFolder, $this->timeFactory, $this->definitions, $this->addressHandler diff --git a/tests/php/Service/RecordingServiceTest.php b/tests/php/Service/RecordingServiceTest.php index e49147526..998e012b1 100644 --- a/tests/php/Service/RecordingServiceTest.php +++ b/tests/php/Service/RecordingServiceTest.php @@ -35,13 +35,19 @@ function is_uploaded_file($filename) { namespace OCA\Talk\Tests\php\Service; +use OCA\Talk\Chat\ChatManager; use OCA\Talk\Config; +use OCA\Talk\Manager; use OCA\Talk\Service\ParticipantService; use OCA\Talk\Service\RecordingService; use OCA\Talk\Service\RoomService; +use OCP\AppFramework\Utility\ITimeFactory; use OCP\Files\IMimeTypeDetector; use OCP\Files\IRootFolder; +use OCP\Notification\IManager; +use OCP\Share\IManager as ShareManager; use PHPUnit\Framework\MockObject\MockObject; +use Psr\Log\LoggerInterface; use Test\TestCase; class RecordingServiceTest extends TestCase { @@ -53,8 +59,20 @@ class RecordingServiceTest extends TestCase { private $rootFolder; /** @var Config|MockObject */ private $config; + /** @var IManager|MockObject */ + private $notificationManager; + /** @var Manager|MockObject */ + private $roomManager; + /** @var ITimeFactory|MockObject */ + private |