diff --git a/appinfo/info.xml b/appinfo/info.xml
index fdc4c96394f..9e298e4fb73 100644
--- a/appinfo/info.xml
+++ b/appinfo/info.xml
@@ -18,7 +18,7 @@
* 🌉 **Sync with other chat solutions** With [Matterbridge](https://github.com/42wim/matterbridge/) being integrated in Talk, you can easily sync a lot of other chat solutions to Nextcloud Talk and vice-versa.
]]>
- 21.0.0-dev.1
+ 21.0.0-dev.2
agpl
Daniel Calviño Sánchez
@@ -64,6 +64,7 @@
OCA\Talk\BackgroundJob\CheckTurnCertificate
OCA\Talk\BackgroundJob\ExpireChatMessages
OCA\Talk\BackgroundJob\ExpireSignalingMessage
+ OCA\Talk\BackgroundJob\MaximumCallDuration
OCA\Talk\BackgroundJob\Reminder
OCA\Talk\BackgroundJob\RemoveEmptyRooms
OCA\Talk\BackgroundJob\ResetAssignedSignalingServer
diff --git a/docs/settings.md b/docs/settings.md
index 35b068585c7..3fd34e61474 100644
--- a/docs/settings.md
+++ b/docs/settings.md
@@ -82,6 +82,7 @@ Legend:
| `bridge_bot_password` | string | | No | | Automatically generated password of the matterbridge bot user profile |
| `default_attachment_folder` | string | `/Talk` | No | | Specify default attachment folder location |
| `start_calls` | int | `0` | Yes | 🖌️ | Who can start a call, see [constants list](constants.md#start-call) |
+| `max_call_duration` | int | `0` | No | ️ | Maximum duration of a call in seconds, 0 for unlimited |
| `max-gif-size` | int | `3145728` | No | | Maximum file size for clients to render gifs previews with animation |
| `session-ping-limit` | int | `200` | No | | Number of sessions the HPB can ping in a single request |
| `token_entropy` | int | `8` | No | | Length of conversation tokens, can be increased to make tokens harder to guess but reduces readability and dial-in comfort |
diff --git a/lib/BackgroundJob/MaximumCallDuration.php b/lib/BackgroundJob/MaximumCallDuration.php
new file mode 100644
index 00000000000..7444e691230
--- /dev/null
+++ b/lib/BackgroundJob/MaximumCallDuration.php
@@ -0,0 +1,57 @@
+setInterval(1);
+ }
+
+ protected function run($argument): void {
+ $maxCallDuration = $this->appConfig->getAppValueInt('max_call_duration');
+ if ($maxCallDuration <= 0) {
+ return;
+ }
+
+ $now = $this->time->getDateTime();
+ $maxActiveSince = $now->sub(new \DateInterval('PT' . $maxCallDuration . 'S'));
+ $rooms = $this->manager->getRoomsLongerActiveSince($maxActiveSince);
+
+ foreach ($rooms as $room) {
+ if ($room->isFederatedConversation()) {
+ continue;
+ }
+
+ $result = $this->roomService->resetActiveSinceInDatabaseOnly($room);
+ if (!$result) {
+ // Someone else won the race condition, make sure this user disconnects directly and then return
+ continue;
+ }
+
+ $this->participantService->endCallForEveryone($room, null);
+ $this->roomService->resetActiveSinceInModelOnly($room);
+ }
+ }
+}
diff --git a/lib/Manager.php b/lib/Manager.php
index 14932633c03..0aea79bb11a 100644
--- a/lib/Manager.php
+++ b/lib/Manager.php
@@ -294,6 +294,28 @@ public function searchRoomsByToken(string $searchToken = '', ?int $limit = null,
return $rooms;
}
+ /**
+ * @return Room[]
+ */
+ public function getRoomsLongerActiveSince(\DateTime $maxActiveSince): array {
+ $query = $this->db->getQueryBuilder();
+ $helper = new SelectHelper();
+ $helper->selectRoomsTable($query);
+ $query->from('talk_rooms', 'r')
+ ->where($query->expr()->isNotNull('r.active_since'))
+ ->andWhere($query->expr()->lte('r.active_since', $query->createNamedParameter($maxActiveSince, IQueryBuilder::PARAM_DATE)))
+ ->orderBy('r.id', 'ASC');
+ $result = $query->executeQuery();
+
+ $rooms = [];
+ while ($row = $result->fetch()) {
+ $rooms[] = $this->createRoomObject($row);
+ }
+ $result->closeCursor();
+
+ return $rooms;
+ }
+
/**
* @param string $userId
* @param array $sessionIds A list of talk sessions to consider for loading (otherwise no session is loaded)
diff --git a/lib/Service/ParticipantService.php b/lib/Service/ParticipantService.php
index 16e9db52ad4..68a4919c0f9 100644
--- a/lib/Service/ParticipantService.php
+++ b/lib/Service/ParticipantService.php
@@ -1159,7 +1159,7 @@ public function cleanGuestParticipants(Room $room): void {
$this->resetCallStateWhenNeeded($room);
}
- public function endCallForEveryone(Room $room, Participant $moderator): void {
+ public function endCallForEveryone(Room $room, ?Participant $moderator): void {
$oldActiveSince = $room->getActiveSince();
$event = new BeforeCallEndedForEveryoneEvent($room, $moderator, $oldActiveSince);
$this->dispatcher->dispatchTyped($event);