feat: track room state

use cron job to check room state every 15 minutes

fix #79
pull/201/head
sualko 2022-03-16 16:12:33 +01:00
parent ff91186b42
commit e63cbaaed1
11 changed files with 157 additions and 6 deletions

View File

@ -0,0 +1,54 @@
<?php
namespace OCA\BigBlueButton\BackgroundJob;
use OCA\BigBlueButton\BigBlueButton\API;
use OCA\BigBlueButton\Service\RoomNotFound;
use OCA\BigBlueButton\Service\RoomService;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\BackgroundJob\IJobList;
use OCP\BackgroundJob\TimedJob;
class IsRunningJob extends TimedJob {
/** @var IJobList */
private $jobList;
/** @var RoomService */
private $service;
/** @var API */
private $api;
public function __construct(ITimeFactory $time, IJobList $jobList, RoomService $service, API $api) {
parent::__construct($time);
$this->jobList = $jobList;
$this->service = $service;
$this->api = $api;
$this->setInterval(15 * 60);
}
protected function run($argument) {
try {
$room = $this->service->find($argument['id']);
} catch (RoomNotFound $e) {
$this->jobList->remove($this, $argument);
return;
}
if (!$room->running) {
$this->jobList->remove($this, $argument);
return;
}
$isRunning = $this->api->isRunning($room);
if (!$isRunning) {
$this->service->updateRunning($room->id, $isRunning);
$this->jobList->remove($this, $argument);
}
}
}

View File

@ -63,6 +63,8 @@ class HookController extends Controller {
$recordingmarks = \boolval($recordingmarks); $recordingmarks = \boolval($recordingmarks);
$room = $this->getRoom(); $room = $this->getRoom();
$this->service->updateRunning($room->getId(), false);
$this->avatarRepository->clearRoom($room->uid); $this->avatarRepository->clearRoom($room->uid);
$this->eventDispatcher->dispatch(MeetingEndedEvent::class, new MeetingEndedEvent($room, $recordingmarks)); $this->eventDispatcher->dispatch(MeetingEndedEvent::class, new MeetingEndedEvent($room, $recordingmarks));

View File

@ -2,6 +2,7 @@
namespace OCA\BigBlueButton\Controller; namespace OCA\BigBlueButton\Controller;
use OCA\BigBlueButton\BackgroundJob\IsRunningJob;
use OCA\BigBlueButton\BigBlueButton\API; use OCA\BigBlueButton\BigBlueButton\API;
use OCA\BigBlueButton\BigBlueButton\Presentation; use OCA\BigBlueButton\BigBlueButton\Presentation;
use OCA\BigBlueButton\Db\Room; use OCA\BigBlueButton\Db\Room;
@ -12,6 +13,7 @@ use OCA\BigBlueButton\Service\RoomService;
use OCP\AppFramework\Controller; use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\RedirectResponse; use OCP\AppFramework\Http\RedirectResponse;
use OCP\AppFramework\Http\TemplateResponse; use OCP\AppFramework\Http\TemplateResponse;
use OCP\BackgroundJob\IJobList;
use OCP\IRequest; use OCP\IRequest;
use OCP\IURLGenerator; use OCP\IURLGenerator;
use OCP\IUserSession; use OCP\IUserSession;
@ -38,6 +40,9 @@ class JoinController extends Controller {
/** @var Permission */ /** @var Permission */
private $permission; private $permission;
/** @var IJobList */
private $jobList;
public function __construct( public function __construct(
string $appName, string $appName,
IRequest $request, IRequest $request,
@ -45,7 +50,8 @@ class JoinController extends Controller {
IURLGenerator $urlGenerator, IURLGenerator $urlGenerator,
IUserSession $userSession, IUserSession $userSession,
API $api, API $api,
Permission $permission Permission $permission,
IJobList $jobList
) { ) {
parent::__construct($appName, $request); parent::__construct($appName, $request);
@ -54,6 +60,7 @@ class JoinController extends Controller {
$this->userSession = $userSession; $this->userSession = $userSession;
$this->api = $api; $this->api = $api;
$this->permission = $permission; $this->permission = $permission;
$this->jobList = $jobList;
} }
public function setToken(string $token): void { public function setToken(string $token): void {
@ -129,6 +136,8 @@ class JoinController extends Controller {
$creationDate = $this->api->createMeeting($room, $presentation); $creationDate = $this->api->createMeeting($room, $presentation);
$joinUrl = $this->api->createJoinUrl($room, $creationDate, $displayname, $isModerator, $userId); $joinUrl = $this->api->createJoinUrl($room, $creationDate, $displayname, $isModerator, $userId);
$this->markAsRunning($room);
\OCP\Util::addHeader('meta', ['http-equiv' => 'refresh', 'content' => '3;url='.$joinUrl]); \OCP\Util::addHeader('meta', ['http-equiv' => 'refresh', 'content' => '3;url='.$joinUrl]);
return new TemplateResponse($this->appName, 'forward', [ return new TemplateResponse($this->appName, 'forward', [
@ -153,4 +162,18 @@ class JoinController extends Controller {
), ),
]); ]);
} }
private function markAsRunning(Room $room) {
if (!$room->running) {
$this->service->updateRunning($room->getId(), true);
}
if (!$this->jobList->has(IsRunningJob::class, [
'id' => $room->id,
])) {
$this->jobList->add(IsRunningJob::class, [
'id' => $room->id,
]);
}
}
} }

View File

@ -25,6 +25,7 @@ use OCP\AppFramework\Db\Entity;
* @method bool getMediaCheck() * @method bool getMediaCheck()
* @method bool getCleanLayout() * @method bool getCleanLayout()
* @method bool getJoinMuted() * @method bool getJoinMuted()
* @method bool getRunning()
* @method void setUid(string $uid) * @method void setUid(string $uid)
* @method void setName(string $name) * @method void setName(string $name)
* @method void setAttendeePassword(string $pw) * @method void setAttendeePassword(string $pw)
@ -42,6 +43,7 @@ use OCP\AppFramework\Db\Entity;
* @method void setMediaCheck(bool $mediaCheck) * @method void setMediaCheck(bool $mediaCheck)
* @method void setCleanLayout(bool $cleanLayout) * @method void setCleanLayout(bool $cleanLayout)
* @method void setJoinMuted(bool $joinMuted) * @method void setJoinMuted(bool $joinMuted)
* @method void setRunning(bool $running)
*/ */
class Room extends Entity implements JsonSerializable { class Room extends Entity implements JsonSerializable {
public const ACCESS_PUBLIC = 'public'; public const ACCESS_PUBLIC = 'public';
@ -71,6 +73,7 @@ class Room extends Entity implements JsonSerializable {
public $mediaCheck; public $mediaCheck;
public $cleanLayout; public $cleanLayout;
public $joinMuted; public $joinMuted;
public $running;
public function __construct() { public function __construct() {
$this->addType('maxParticipants', 'integer'); $this->addType('maxParticipants', 'integer');
@ -82,6 +85,7 @@ class Room extends Entity implements JsonSerializable {
$this->addType('mediaCheck', 'boolean'); $this->addType('mediaCheck', 'boolean');
$this->addType('cleanLayout', 'boolean'); $this->addType('cleanLayout', 'boolean');
$this->addType('joinMuted', 'boolean'); $this->addType('joinMuted', 'boolean');
$this->addType('running', 'boolean');
} }
public function jsonSerialize(): array { public function jsonSerialize(): array {
@ -103,6 +107,7 @@ class Room extends Entity implements JsonSerializable {
'mediaCheck' => boolval($this->mediaCheck), 'mediaCheck' => boolval($this->mediaCheck),
'cleanLayout' => boolval($this->cleanLayout), 'cleanLayout' => boolval($this->cleanLayout),
'joinMuted' => boolval($this->joinMuted), 'joinMuted' => boolval($this->joinMuted),
'running' => boolval($this->running),
]; ];
} }
} }

View File

@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
namespace OCA\BigBlueButton\Migration;
use Closure;
use OCP\DB\ISchemaWrapper;
use OCP\Migration\IOutput;
use OCP\Migration\SimpleMigrationStep;
/**
* Auto-generated migration step: Please modify to your needs!
*/
class Version000000Date20220316151400 extends SimpleMigrationStep {
/**
* @param IOutput $output
* @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
* @param array $options
* @return null|ISchemaWrapper
*/
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
$schema = $schemaClosure();
if ($schema->hasTable('bbb_rooms')) {
$table = $schema->getTable('bbb_rooms');
if (!$table->hasColumn('running')) {
$table->addColumn('running', 'boolean', [
'notnull' => false,
'default' => false
]);
}
return $schema;
}
return null;
}
}

View File

@ -163,6 +163,21 @@ class RoomService {
} }
} }
/**
* @return \OCP\AppFramework\Db\Entity|null
*/
public function updateRunning(int $id, bool $running) {
try {
$room = $this->mapper->find($id);
$room->setRunning($running);
return $this->mapper->update($room);
} catch (Exception $e) {
$this->handleException($e);
}
}
/** /**
* @return Room|null * @return Room|null
*/ */

View File

@ -11,6 +11,7 @@ use OCA\BigBlueButton\Service\RoomService;
use OCP\AppFramework\Http; use OCP\AppFramework\Http;
use OCP\AppFramework\Http\RedirectResponse; use OCP\AppFramework\Http\RedirectResponse;
use OCP\AppFramework\Http\TemplateResponse; use OCP\AppFramework\Http\TemplateResponse;
use OCP\BackgroundJob\IJobList;
use OCP\IRequest; use OCP\IRequest;
use OCP\IURLGenerator; use OCP\IURLGenerator;
use OCP\IUser; use OCP\IUser;
@ -26,6 +27,7 @@ class JoinControllerTest extends TestCase {
private $api; private $api;
private $permission; private $permission;
private $room; private $room;
private $jobList;
public function setUp(): void { public function setUp(): void {
parent::setUp(); parent::setUp();
@ -36,6 +38,7 @@ class JoinControllerTest extends TestCase {
$this->urlGenerator = $this->createMock(IURLGenerator::class); $this->urlGenerator = $this->createMock(IURLGenerator::class);
$this->api = $this->createMock(API::class); $this->api = $this->createMock(API::class);
$this->permission = $this->createMock(Permission::class); $this->permission = $this->createMock(Permission::class);
$this->jobList = $this->createMock(IJobList::class);
$this->controller = new JoinController( $this->controller = new JoinController(
'bbb', 'bbb',
@ -44,10 +47,12 @@ class JoinControllerTest extends TestCase {
$this->urlGenerator, $this->urlGenerator,
$this->userSession, $this->userSession,
$this->api, $this->api,
$this->permission $this->permission,
$this->jobList
); );
$this->room = new Room(); $this->room = new Room();
$this->room->id = 1;
$this->room->uid = 'uid_foo'; $this->room->uid = 'uid_foo';
$this->room->userId = 'user_foo'; $this->room->userId = 'user_foo';
$this->room->access = Room::ACCESS_PUBLIC; $this->room->access = Room::ACCESS_PUBLIC;

View File

@ -44,6 +44,7 @@ export interface Room {
mediaCheck: boolean, mediaCheck: boolean,
cleanLayout: boolean, cleanLayout: boolean,
joinMuted: boolean, joinMuted: boolean,
running: boolean,
} }
export interface RoomShare { export interface RoomShare {

View File

@ -184,6 +184,10 @@ pre {
width: 42px; width: 42px;
padding: 0; padding: 0;
} }
&.name {
width: 100%;
}
} }
tfoot td { tfoot td {

View File

@ -187,9 +187,9 @@ const RoomRow: React.FC<Props> = (props) => {
return ( return (
<> <>
<tr className={showRecordings ? 'selected-row' : ''}> <tr className={showRecordings ? 'selected-row' : ''}>
<td className="start icon-col"> <td className="start">
<a href={api.getRoomUrl(room)} className="action-item" target="_blank" rel="noopener noreferrer" title={t('bbb', 'Open room')}> <a href={api.getRoomUrl(room)} className="button primary" target="_blank" rel="noopener noreferrer" title={t('bbb', 'Open room')}>
<span className="icon icon-play icon-visible"></span> {room.running ? t('bbb', 'Join') : t('bbb', 'Start')}
</a> </a>
</td> </td>
<td className="share icon-col"> <td className="share icon-col">

View File

@ -65,7 +65,7 @@ async function openDialog(fileId: number, filename: string) {
const row = $('<tr>'); const row = $('<tr>');
const button = $('<button>'); const button = $('<button>');
button.text(t('bbb', 'Start')); button.text(room.running ? t('bbb', 'Send to') : t('bbb', 'Start with'));
button.addClass('primary'); button.addClass('primary');
button.attr('type', 'button'); button.attr('type', 'button');
button.on('click', (ev) => { button.on('click', (ev) => {