Signed-off-by: Joas Schilling <coding@schilljs.com>
This commit is contained in:
Joas Schilling 2017-10-10 17:09:37 +02:00
Родитель 0cdb0cc211
Коммит 8ef70af937
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: E166FD8976B3BAC8
9 изменённых файлов: 536 добавлений и 64 удалений

Просмотреть файл

@ -42,7 +42,7 @@ And in the works for the [coming versions](https://github.com/nextcloud/spreed/m
<bugs>https://github.com/nextcloud/spreed/issues</bugs> <bugs>https://github.com/nextcloud/spreed/issues</bugs>
<repository type="git">https://github.com/nextcloud/spreed.git</repository> <repository type="git">https://github.com/nextcloud/spreed.git</repository>
<version>2.1.6</version> <version>2.1.7</version>
<dependencies> <dependencies>
<nextcloud min-version="13" max-version="13" /> <nextcloud min-version="13" max-version="13" />
@ -72,7 +72,8 @@ And in the works for the [coming versions](https://github.com/nextcloud/spreed/m
</settings> </settings>
<providers> <providers>
<provider>OCA\Spreed\Activity\Provider</provider> <provider>OCA\Spreed\Activity\Provider\Invitation</provider>
<provider>OCA\Spreed\Activity\Provider\Call</provider>
</providers> </providers>
</activity> </activity>

86
lib/Activity/Hooks.php Normal file
Просмотреть файл

@ -0,0 +1,86 @@
<?php
/**
* @copyright Copyright (c) 2017 Joas Schilling <coding@schilljs.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Spreed\Activity;
use OCA\Spreed\Room;
use OCP\Activity\IManager;
use OCP\AppFramework\Utility\ITimeFactory;
class Hooks {
/** @var IManager */
protected $activityManager;
/** @var ITimeFactory */
protected $timeFactory;
/**
* @param IManager $activityManager
* @param ITimeFactory $timeFactory
*/
public function __construct(IManager $activityManager, ITimeFactory $timeFactory) {
$this->activityManager = $activityManager;
$this->timeFactory = $timeFactory;
}
public function setActive(Room $room, $isGuest) {
$room->setActiveSince(new \DateTime(), $isGuest);
}
public function generateActivity(Room $room) {
$activeSince = $room->getActiveSince();
if (!$activeSince instanceof \DateTime || $room->hasActiveSessions()) {
return false;
}
$duration = $this->timeFactory->getTime() - $activeSince->getTimestamp();
$participants = $room->getParticipants($activeSince->getTimestamp());
$userIds = array_keys($participants['users']);
if (empty($userIds) || (count($userIds) === 1 && $room->getActiveGuests() === 0)) {
// Single user pinged or guests only => no activity
$room->resetActiveSince();
return false;
}
$event = $this->activityManager->generateEvent();
$event->setApp('spreed')
->setType('spreed')
->setAuthor('')
->setObject('room', $room->getId(), $room->getName())
->setTimestamp(time())
->setSubject('call', [
'room' => $room->getId(),
'users' => $userIds,
'guests' => $room->getActiveGuests(),
'duration' => $duration,
]);
foreach ($userIds as $userId) {
$event->setAffectedUser($userId);
$this->activityManager->publish($event);
}
$room->resetActiveSince();
}
}

Просмотреть файл

@ -19,7 +19,7 @@
* *
*/ */
namespace OCA\Spreed\Activity; namespace OCA\Spreed\Activity\Provider;
use OCA\Spreed\Exceptions\RoomNotFoundException; use OCA\Spreed\Exceptions\RoomNotFoundException;
use OCA\Spreed\Manager; use OCA\Spreed\Manager;
@ -32,7 +32,7 @@ use OCP\IUser;
use OCP\IUserManager; use OCP\IUserManager;
use OCP\L10N\IFactory; use OCP\L10N\IFactory;
class Provider implements IProvider { abstract class Base implements IProvider {
/** @var IFactory */ /** @var IFactory */
protected $languageFactory; protected $languageFactory;
@ -68,49 +68,20 @@ class Provider implements IProvider {
} }
/** /**
* @param string $language
* @param IEvent $event * @param IEvent $event
* @param IEvent|null $previousEvent
* @return IEvent * @return IEvent
* @throws \InvalidArgumentException * @throws \InvalidArgumentException
* @since 11.0.0
*/ */
public function parse($language, IEvent $event, IEvent $previousEvent = null) { public function preParse(IEvent $event) {
if ($event->getApp() !== 'spreed') { if ($event->getApp() !== 'spreed') {
throw new \InvalidArgumentException(); throw new \InvalidArgumentException();
} }
if ($this->activityManager->getRequirePNG()) {
$l = $this->languageFactory->get('spreed', $language);
if (method_exists($this->activityManager, 'getRequirePNG') && $this->activityManager->getRequirePNG()) {
$event->setIcon($this->url->getAbsoluteURL($this->url->imagePath('spreed', 'app-dark.png'))); $event->setIcon($this->url->getAbsoluteURL($this->url->imagePath('spreed', 'app-dark.png')));
} else { } else {
$event->setIcon($this->url->getAbsoluteURL($this->url->imagePath('spreed', 'app-dark.svg'))); $event->setIcon($this->url->getAbsoluteURL($this->url->imagePath('spreed', 'app-dark.svg')));
} }
try {
$parameters = $event->getSubjectParameters();
$room = $this->manager->getRoomById((int) $parameters['room']);
if ($room->getName() === '') {
if ($room->getType() === Room::ONE_TO_ONE_CALL) {
$parsedParameters = $this->getParameters($parameters);
$subject = $l->t('{actor} invited you to a private call');
} else {
$parsedParameters = $this->getParameters($parameters);
$subject = $l->t('{actor} invited you to a group call');
}
} else {
$parsedParameters = $this->getParameters($parameters, $room);
$subject = $l->t('{actor} invited you to the call {call}');
}
} catch (RoomNotFoundException $e) {
throw new \InvalidArgumentException();
}
$this->setSubjects($event, $subject, $parsedParameters);
return $event; return $event;
} }
@ -131,24 +102,6 @@ class Provider implements IProvider {
->setRichSubject($subject, $parameters); ->setRichSubject($subject, $parameters);
} }
/**
* @param array $parameters
* @param Room $room
* @return array
*/
protected function getParameters(array $parameters, Room $room = null) {
if ($room === null) {
return [
'actor' => $this->getUser($parameters['user']),
];
} else {
return [
'actor' => $this->getUser($parameters['user']),
'call' => $this->getRoom($room),
];
}
}
/** /**
* @param Room $room * @param Room $room
* @return array * @return array
@ -199,8 +152,7 @@ class Provider implements IProvider {
$user = $this->userManager->get($uid); $user = $this->userManager->get($uid);
if ($user instanceof IUser) { if ($user instanceof IUser) {
return $user->getDisplayName(); return $user->getDisplayName();
} else { }
return $uid; return $uid;
} }
} }
}

Просмотреть файл

@ -0,0 +1,163 @@
<?php
/**
* @copyright Copyright (c) 2017 Joas Schilling <coding@schilljs.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Spreed\Activity\Provider;
use OCA\Spreed\Exceptions\RoomNotFoundException;
use OCA\Spreed\Room;
use OCP\Activity\IEvent;
use OCP\IL10N;
class Call extends Base {
/**
* @param string $language
* @param IEvent $event
* @param IEvent|null $previousEvent
* @return IEvent
* @throws \InvalidArgumentException
* @since 11.0.0
*/
public function parse($language, IEvent $event, IEvent $previousEvent = null) {
$event = parent::preParse($event);
$l = $this->languageFactory->get('spreed', $language);
try {
$parameters = $event->getSubjectParameters();
$room = $this->manager->getRoomById((int) $parameters['room']);
if ($event->getSubject() === 'call') {
$result = $this->parseCall($event, $l, $room);
$result['subject'] .= ' ' . $this->getDuration($l, $parameters['duration']);
$this->setSubjects($event, $result['subject'], $result['params']);
} else {
throw new \InvalidArgumentException();
}
} catch (RoomNotFoundException $e) {
throw new \InvalidArgumentException();
}
return $event;
}
protected function getDuration(IL10N $l, $seconds) {
$hours = floor($seconds / 3600);
$seconds %= 3600;
$minutes = floor($seconds / 60);
$seconds %= 60;
if ($hours > 0) {
$duration = sprintf('%1$d:%2$02d:%3$02d', $hours, $minutes, $seconds);
} else {
$duration = sprintf('%1$d:%2$02d', $minutes, $seconds);
}
return $l->t('(Duration %s)', $duration);
}
protected function parseCall(IEvent $event, IL10N $l, Room $room) {
$parameters = $event->getSubjectParameters();
$currentUser = array_search($this->activityManager->getCurrentUserId(), $parameters['users'], true);
if ($currentUser === false) {
throw new \InvalidArgumentException('Unknown case');
}
unset($parameters['users'][$currentUser]);
sort($parameters['users']);
if ($room->getType() === Room::ONE_TO_ONE_CALL) {
$otherUser = array_pop($parameters['users']);
if ($otherUser === '') {
throw new \InvalidArgumentException('Unknown case');
}
return [
'subject' => $l->t('You had a private call with {user}'),
'params' => [
'user' => $this->getUser($otherUser),
],
];
}
$numUsers = count($parameters['users']);
$displayedUsers = $numUsers;
switch ($numUsers) {
case 0:
$subject = $l->t('You had a call with {user1}');
$subject = str_replace('{user1}', $l->n('%n guest', '%n guests', $parameters['guests']), $subject);
break;
case 1:
if ($parameters['guests'] === 0) {
$subject = $l->t('You had a call with {user1}');
} else {
$subject = $l->t('You had a call with {user1} and {user2}');
$subject = str_replace('{user2}', $l->n('%n guest', '%n guests', $parameters['guests']), $subject);
}
break;
case 2:
if ($parameters['guests'] === 0) {
$subject = $l->t('You had a call with {user1} and {user2}');
} else {
$subject = $l->t('You had a call with {user1}, {user2} and {user3}');
$subject = str_replace('{user3}', $l->n('%n guest', '%n guests', $parameters['guests']), $subject);
}
break;
case 3:
if ($parameters['guests'] === 0) {
$subject = $l->t('You had a call with {user1}, {user2} and {user3}');
} else {
$subject = $l->t('You had a call with {user1}, {user2}, {user3} and {user4}');
$subject = str_replace('{user4}', $l->n('%n guest', '%n guests', $parameters['guests']), $subject);
}
break;
case 4:
if ($parameters['guests'] === 0) {
$subject = $l->t('You had a call with {user1}, {user2}, {user3} and {user4}');
} else {
$subject = $l->t('You had a call with {user1}, {user2}, {user3}, {user4} and {user5}');
$subject = str_replace('{user5}', $l->n('%n guest', '%n guests', $parameters['guests']), $subject);
}
break;
case 5:
default:
$subject = $l->t('You had a call with {user1}, {user2}, {user3}, {user4} and {user5}');
if ($numUsers === 5 && $parameters['guests'] === 0) {
$displayedUsers = 5;
} else {
$displayedUsers = 4;
$numOthers = $parameters['guests'] + $numUsers - $displayedUsers;
$subject = str_replace('{user5}', $l->n('%n other', '%n others', $numOthers), $subject);
}
}
$params = [];
for ($i = 1; $i <= $displayedUsers; $i++) {
$params['user' . $i] = $this->getUser($parameters['users'][$i - 1]);
}
return [
'subject' => $subject,
'params' => $params,
];
}
}

Просмотреть файл

@ -0,0 +1,100 @@
<?php
/**
* @copyright Copyright (c) 2017 Joas Schilling <coding@schilljs.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Spreed\Activity\Provider;
use OCA\Spreed\Exceptions\RoomNotFoundException;
use OCA\Spreed\Room;
use OCP\Activity\IEvent;
use OCP\IL10N;
class Invitation extends Base {
/**
* @param string $language
* @param IEvent $event
* @param IEvent|null $previousEvent
* @return IEvent
* @throws \InvalidArgumentException
* @since 11.0.0
*/
public function parse($language, IEvent $event, IEvent $previousEvent = null) {
$event = parent::preParse($event);
$l = $this->languageFactory->get('spreed', $language);
try {
$parameters = $event->getSubjectParameters();
$room = $this->manager->getRoomById((int) $parameters['room']);
if ($event->getSubject() === 'invitation') {
$result = $this->parseInvitation($event, $l, $room);
$this->setSubjects($event, $result['subject'], $result['params']);
} else {
throw new \InvalidArgumentException();
}
} catch (RoomNotFoundException $e) {
throw new \InvalidArgumentException();
}
return $event;
}
protected function parseInvitation(IEvent $event, IL10N $l, Room $room) {
$parameters = $event->getSubjectParameters();
if ($room->getName() === '') {
if ($room->getType() === Room::ONE_TO_ONE_CALL) {
$subject = $l->t('{actor} invited you to a private call');
} else {
$subject = $l->t('{actor} invited you to a group call');
}
return [
'subject' => $subject,
'params' => $this->getParameters($parameters),
];
}
return [
'subject' => $l->t('{actor} invited you to the call {call}'),
'params' => $this->getParameters($parameters, $room),
];
}
/**
* @param array $parameters
* @param Room $room
* @return array
*/
protected function getParameters(array $parameters, Room $room = null) {
if ($room === null) {
return [
'actor' => $this->getUser($parameters['user']),
];
}
return [
'actor' => $this->getUser($parameters['user']),
'call' => $this->getRoom($room),
];
}
}

Просмотреть файл

@ -21,9 +21,11 @@
namespace OCA\Spreed\AppInfo; namespace OCA\Spreed\AppInfo;
use OCA\Spreed\Activity\Hooks;
use OCA\Spreed\Room; use OCA\Spreed\Room;
use OCA\Spreed\Signaling\Messages; use OCA\Spreed\Signaling\Messages;
use OCP\AppFramework\App; use OCP\AppFramework\App;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\GenericEvent; use Symfony\Component\EventDispatcher\GenericEvent;
class Application extends App { class Application extends App {
@ -48,5 +50,32 @@ class Application extends App {
$dispatcher->addListener(Room::class . '::postRemoveUser', $listener); $dispatcher->addListener(Room::class . '::postRemoveUser', $listener);
$dispatcher->addListener(Room::class . '::postRemoveBySession', $listener); $dispatcher->addListener(Room::class . '::postRemoveBySession', $listener);
$dispatcher->addListener(Room::class . '::postUserDisconnectRoom', $listener); $dispatcher->addListener(Room::class . '::postUserDisconnectRoom', $listener);
$this->registerActivityHooks($dispatcher);
}
protected function registerActivityHooks(EventDispatcherInterface $dispatcher) {
$listener = function(GenericEvent $event, $eventName) {
/** @var Room $room */
$room = $event->getSubject();
/** @var Hooks $hooks */
$hooks = $this->getContainer()->query(Hooks::class);
$hooks->setActive($room, $eventName === Room::class . '::postGuestEnterRoom');
};
$dispatcher->addListener(Room::class . '::postUserEnterRoom', $listener);
$dispatcher->addListener(Room::class . '::postGuestEnterRoom', $listener);
$listener = function(GenericEvent $event) {
/** @var Room $room */
$room = $event->getSubject();
/** @var Hooks $hooks */
$hooks = $this->getContainer()->query(Hooks::class);
$hooks->generateActivity($room);
};
$dispatcher->addListener(Room::class . '::postRemoveBySession', $listener);
$dispatcher->addListener(Room::class . '::postRemoveUser', $listener);
$dispatcher->addListener(Room::class . '::postUserDisconnectRoom', $listener);
} }
} }

Просмотреть файл

@ -65,7 +65,12 @@ class Manager {
* @return Room * @return Room
*/ */
protected function createRoomObject(array $row) { protected function createRoomObject(array $row) {
return new Room($this->db, $this->secureRandom, $this->dispatcher, $this->hasher, (int) $row['id'], (int) $row['type'], $row['token'], $row['name'], $row['password']); $activeSince = null;
if (!empty($row['activeSince'])) {
$activeSince = new \DateTime($row['activeSince']);
}
return new Room($this->db, $this->secureRandom, $this->dispatcher, $this->hasher, (int) $row['id'], (int) $row['type'], $row['token'], $row['name'], $row['password'], (int) $row['activeGuests'], $activeSince);
} }
/** /**
@ -358,13 +363,7 @@ class Manager {
$query->execute(); $query->execute();
$roomId = $query->getLastInsertId(); $roomId = $query->getLastInsertId();
return $this->createRoomObject([ return $this->getRoomById($roomId);
'id' => $roomId,
'type' => $type,
'token' => $token,
'name' => $name,
'password' => '',
]);
} }
/** /**

Просмотреть файл

@ -0,0 +1,57 @@
<?php
/**
* @copyright Copyright (c) 2017 Joas Schilling <coding@schilljs.com>
*
* @author Joas Schilling <coding@schilljs.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Spreed\Migration;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\DBAL\Types\Type;
use OCP\Migration\SimpleMigrationStep;
use OCP\Migration\IOutput;
class Version2001Date20171009132424 extends SimpleMigrationStep {
/**
* @param IOutput $output
* @param \Closure $schemaClosure The `\Closure` returns a `Schema`
* @param array $options
* @return null|Schema
* @since 13.0.0
*/
public function changeSchema(IOutput $output, \Closure $schemaClosure, array $options) {
/** @var Schema $schema */
$schema = $schemaClosure();
$table = $schema->getTable('spreedme_rooms');
$table->addColumn('activeSince', Type::DATETIME, [
'notnull' => false,
]);
$table->addColumn('activeGuests', Type::INTEGER, [
'notnull' => true,
'length' => 4,
'default' => 0,
'unsigned' => true,
]);
return $schema;
}
}

Просмотреть файл

@ -59,6 +59,10 @@ class Room {
private $name; private $name;
/** @var string */ /** @var string */
private $password; private $password;
/** @var int */
private $activeGuests;
/** @var \DateTime|null */
private $activeSince;
/** @var string */ /** @var string */
protected $currentUser; protected $currentUser;
@ -77,8 +81,10 @@ class Room {
* @param string $token * @param string $token
* @param string $name * @param string $name
* @param string $password * @param string $password
* @param int $activeGuests
* @param \DateTime|null $activeSince
*/ */
public function __construct(IDBConnection $db, ISecureRandom $secureRandom, EventDispatcherInterface $dispatcher, IHasher $hasher, $id, $type, $token, $name, $password) { public function __construct(IDBConnection $db, ISecureRandom $secureRandom, EventDispatcherInterface $dispatcher, IHasher $hasher, $id, $type, $token, $name, $password, $activeGuests, \DateTime $activeSince = null) {
$this->db = $db; $this->db = $db;
$this->secureRandom = $secureRandom; $this->secureRandom = $secureRandom;
$this->dispatcher = $dispatcher; $this->dispatcher = $dispatcher;
@ -88,6 +94,8 @@ class Room {
$this->token = $token; $this->token = $token;
$this->name = $name; $this->name = $name;
$this->password = $password; $this->password = $password;
$this->activeGuests = $activeGuests;
$this->activeSince = $activeSince;
} }
/** /**
@ -118,6 +126,20 @@ class Room {
return $this->name; return $this->name;
} }
/**
* @return int
*/
public function getActiveGuests() {
return $this->activeGuests;
}
/**
* @return \DateTime|null
*/
public function getActiveSince() {
return $this->activeSince;
}
/** /**
* @return bool * @return bool
*/ */
@ -277,6 +299,52 @@ class Room {
return true; return true;
} }
/**
* @param \DateTime $since
* @param bool $isGuest
* @return bool
*/
public function setActiveSince(\DateTime $since, $isGuest) {
if ($isGuest && $this->getType() === self::PUBLIC_CALL) {
$query = $this->db->getQueryBuilder();
$query->update('spreedme_rooms')
->set('activeGuests', $query->createFunction($query->getColumnName('activeGuests') . ' + 1'))
->where($query->expr()->eq('id', $query->createNamedParameter($this->getId(), IQueryBuilder::PARAM_INT)));
$query->execute();
$this->activeGuests++;
}
if ($this->activeSince instanceof \DateTime) {
return false;
}
$query = $this->db->getQueryBuilder();
$query->update('spreedme_rooms')
->set('activeSince', $query->createNamedParameter($since, 'datetime'))
->where($query->expr()->eq('id', $query->createNamedParameter($this->getId(), IQueryBuilder::PARAM_INT)))
->andWhere($query->expr()->isNull('activeSince'));
$query->execute();
$this->activeSince = $since;
return true;
}
/**
* @return bool
*/
public function resetActiveSince() {
$query = $this->db->getQueryBuilder();
$query->update('spreedme_rooms')
->set('activeGuests', $query->createNamedParameter(0))
->set('activeSince', $query->createNamedParameter(null, 'datetime'))
->where($query->expr()->eq('id', $query->createNamedParameter($this->getId(), IQueryBuilder::PARAM_INT)));
$query->execute();
}
/** /**
* @param int $newType Currently it is only allowed to change to: self::GROUP_CALL, self::PUBLIC_CALL * @param int $newType Currently it is only allowed to change to: self::GROUP_CALL, self::PUBLIC_CALL
* @return bool True when the change was valid, false otherwise * @return bool True when the change was valid, false otherwise
@ -601,6 +669,23 @@ class Room {
return $sessions; return $sessions;
} }
/**
* @return bool
*/
public function hasActiveSessions() {
$query = $this->db->getQueryBuilder();
$query->select('sessionId')
->from('spreedme_room_participants')
->where($query->expr()->eq('roomId', $query->createNamedParameter($this->getId(), IQueryBuilder::PARAM_INT)))
->andWhere($query->expr()->neq('sessionId', $query->createNamedParameter('0')))
->setMaxResults(1);
$result = $query->execute();
$row = $result->fetch();
$result->closeCursor();
return (bool) $row;
}
/** /**
* @param int $lastPing When the last ping is older than the given timestamp, the user is ignored * @param int $lastPing When the last ping is older than the given timestamp, the user is ignored
* @return int * @return int