You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

424 lines
13 KiB

  1. <?php
  2. /**
  3. * @copyright Copyright (c) 2016 Lukas Reschke <lukas@statuscode.ch>
  4. * @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com>
  5. *
  6. * @author Lukas Reschke <lukas@statuscode.ch>
  7. * @author Joas Schilling <coding@schilljs.com>
  8. *
  9. * @license GNU AGPL version 3 or any later version
  10. *
  11. * This program is free software: you can redistribute it and/or modify
  12. * it under the terms of the GNU Affero General Public License as
  13. * published by the Free Software Foundation, either version 3 of the
  14. * License, or (at your option) any later version.
  15. *
  16. * This program is distributed in the hope that it will be useful,
  17. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  18. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  19. * GNU Affero General Public License for more details.
  20. *
  21. * You should have received a copy of the GNU Affero General Public License
  22. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  23. *
  24. */
  25. namespace OCA\Spreed;
  26. use OCP\DB\QueryBuilder\IQueryBuilder;
  27. use OCP\IDBConnection;
  28. use OCP\IUser;
  29. use OCP\Security\ISecureRandom;
  30. class Room {
  31. const ONE_TO_ONE_CALL = 1;
  32. const GROUP_CALL = 2;
  33. const PUBLIC_CALL = 3;
  34. /** @var IDBConnection */
  35. private $db;
  36. /** @var ISecureRandom */
  37. private $secureRandom;
  38. /** @var int */
  39. private $id;
  40. /** @var int */
  41. private $type;
  42. /** @var string */
  43. private $token;
  44. /** @var string */
  45. private $name;
  46. /** @var string */
  47. protected $currentUser;
  48. /** @var Participant */
  49. protected $participant;
  50. /**
  51. * Room constructor.
  52. *
  53. * @param IDBConnection $db
  54. * @param ISecureRandom $secureRandom
  55. * @param int $id
  56. * @param int $type
  57. * @param string $token
  58. * @param string $name
  59. */
  60. public function __construct(IDBConnection $db, ISecureRandom $secureRandom, $id, $type, $token, $name) {
  61. $this->db = $db;
  62. $this->secureRandom = $secureRandom;
  63. $this->id = $id;
  64. $this->type = $type;
  65. $this->token = $token;
  66. $this->name = $name;
  67. }
  68. /**
  69. * @return int
  70. */
  71. public function getId() {
  72. return $this->id;
  73. }
  74. /**
  75. * @return int
  76. */
  77. public function getType() {
  78. return $this->type;
  79. }
  80. /**
  81. * @return string
  82. */
  83. public function getToken() {
  84. return $this->token;
  85. }
  86. /**
  87. * @return string
  88. */
  89. public function getName() {
  90. return $this->name;
  91. }
  92. /**
  93. * @param string $userId
  94. * @param Participant $participant
  95. */
  96. public function setParticipant($userId, Participant $participant) {
  97. $this->currentUser = $userId;
  98. $this->participant = $participant;
  99. }
  100. /**
  101. * @param string $userId
  102. * @return Participant
  103. * @throws \RuntimeException When the user is not a participant
  104. */
  105. public function getParticipant($userId) {
  106. if (!is_string($userId) || $userId === '') {
  107. throw new \RuntimeException('Not a user');
  108. }
  109. if ($this->currentUser === $userId && $this->participant instanceof Participant) {
  110. return $this->participant;
  111. }
  112. $query = $this->db->getQueryBuilder();
  113. $query->select('*')
  114. ->from('spreedme_room_participants')
  115. ->where($query->expr()->eq('userId', $query->createNamedParameter($userId)))
  116. ->andWhere($query->expr()->eq('roomId', $query->createNamedParameter($this->getId())));
  117. $result = $query->execute();
  118. $row = $result->fetch();
  119. $result->closeCursor();
  120. if ($row === false) {
  121. throw new \RuntimeException('User is not a participant');
  122. }
  123. if ($this->currentUser === $userId) {
  124. $this->participant = new Participant($this->db, $this, $row['userId'], (int) $row['participantType'], (int) $row['lastPing'], $row['sessionId']);
  125. return $this->participant;
  126. }
  127. return new Participant($this->db, $this, $row['userId'], (int) $row['participantType'], (int) $row['lastPing'], $row['sessionId']);
  128. }
  129. public function deleteRoom() {
  130. $query = $this->db->getQueryBuilder();
  131. // Delete all participants
  132. $query->delete('spreedme_room_participants')
  133. ->where($query->expr()->eq('roomId', $query->createNamedParameter($this->getId(), IQueryBuilder::PARAM_INT)));
  134. $query->execute();
  135. // Delete room
  136. $query->delete('spreedme_rooms')
  137. ->where($query->expr()->eq('id', $query->createNamedParameter($this->getId(), IQueryBuilder::PARAM_INT)));
  138. $query->execute();
  139. }
  140. /**
  141. * @param string $newName Currently it is only allowed to rename: Room::GROUP_CALL, Room::PUBLIC_CALL
  142. * @return bool True when the change was valid, false otherwise
  143. */
  144. public function setName($newName) {
  145. if ($newName === $this->getName()) {
  146. return true;
  147. }
  148. if ($this->getType() === self::ONE_TO_ONE_CALL) {
  149. return false;
  150. }
  151. $query = $this->db->getQueryBuilder();
  152. $query->update('spreedme_rooms')
  153. ->set('name', $query->createNamedParameter($newName))
  154. ->where($query->expr()->eq('id', $query->createNamedParameter($this->getId(), IQueryBuilder::PARAM_INT)));
  155. $query->execute();
  156. return true;
  157. }
  158. /**
  159. * @param int $newType Currently it is only allowed to change to: Room::GROUP_CALL, Room::PUBLIC_CALL
  160. * @return bool True when the change was valid, false otherwise
  161. */
  162. public function changeType($newType) {
  163. if ($newType === $this->getType()) {
  164. return true;
  165. }
  166. if (!in_array($newType, [Room::GROUP_CALL, Room::PUBLIC_CALL], true)) {
  167. return false;
  168. }
  169. $oldType = $this->getType();
  170. $query = $this->db->getQueryBuilder();
  171. $query->update('spreedme_rooms')
  172. ->set('type', $query->createNamedParameter($newType, IQueryBuilder::PARAM_INT))
  173. ->where($query->expr()->eq('id', $query->createNamedParameter($this->getId(), IQueryBuilder::PARAM_INT)));
  174. $query->execute();
  175. $this->type = (int) $newType;
  176. if ($oldType === Room::PUBLIC_CALL) {
  177. // Kick all guests
  178. $query = $this->db->getQueryBuilder();
  179. $query->delete('spreedme_room_participants')
  180. ->where($query->expr()->eq('roomId', $query->createNamedParameter($this->getId(), IQueryBuilder::PARAM_INT)))
  181. ->andWhere($query->expr()->emptyString('userId'));
  182. $query->execute();
  183. }
  184. return true;
  185. }
  186. /**
  187. * @param IUser $user
  188. */
  189. public function addUser(IUser $user) {
  190. $this->addParticipant($user->getUID(), Participant::USER);
  191. }
  192. /**
  193. * @param string $participant
  194. * @param int $participantType
  195. * @param string $sessionId
  196. */
  197. public function addParticipant($participant, $participantType, $sessionId = '0') {
  198. $query = $this->db->getQueryBuilder();
  199. $query->insert('spreedme_room_participants')
  200. ->values(
  201. [
  202. 'userId' => $query->createNamedParameter($participant),
  203. 'roomId' => $query->createNamedParameter($this->getId()),
  204. 'lastPing' => $query->createNamedParameter(0, IQueryBuilder::PARAM_INT),
  205. 'sessionId' => $query->createNamedParameter($sessionId),
  206. 'participantType' => $query->createNamedParameter($participantType, IQueryBuilder::PARAM_INT),
  207. ]
  208. );
  209. $query->execute();
  210. }
  211. /**
  212. * @param string $participant
  213. * @param int $participantType
  214. */
  215. public function setParticipantType($participant, $participantType) {
  216. $query = $this->db->getQueryBuilder();
  217. $query->update('spreedme_room_participants')
  218. ->set('participantType', $query->createNamedParameter($participantType, IQueryBuilder::PARAM_INT))
  219. ->where($query->expr()->eq('roomId', $query->createNamedParameter($this->getId(), IQueryBuilder::PARAM_INT)))
  220. ->andWhere($query->expr()->eq('userId', $query->createNamedParameter($participant)));
  221. $query->execute();
  222. }
  223. /**
  224. * @param IUser $user
  225. */
  226. public function removeUser(IUser $user) {
  227. $query = $this->db->getQueryBuilder();
  228. $query->delete('spreedme_room_participants')
  229. ->where($query->expr()->eq('roomId', $query->createNamedParameter($this->getId(), IQueryBuilder::PARAM_INT)))
  230. ->andWhere($query->expr()->eq('userId', $query->createNamedParameter($user->getUID())));
  231. $query->execute();
  232. }
  233. /**
  234. * @param string $userId
  235. * @return string
  236. */
  237. public function enterRoomAsUser($userId) {
  238. $query = $this->db->getQueryBuilder();
  239. $query->update('spreedme_room_participants')
  240. ->set('sessionId', $query->createParameter('sessionId'))
  241. ->where($query->expr()->eq('roomId', $query->createNamedParameter($this->getId(), IQueryBuilder::PARAM_INT)))
  242. ->andWhere($query->expr()->eq('userId', $query->createNamedParameter($userId)));
  243. $sessionId = $this->secureRandom->generate(255);
  244. $query->setParameter('sessionId', $sessionId);
  245. $result = $query->execute();
  246. if ($result === 0) {
  247. // User joining a public room, without being invited
  248. $this->addParticipant($userId, $sessionId);
  249. }
  250. while (!$this->isSessionUnique($sessionId)) {
  251. $sessionId = $this->secureRandom->generate(255);
  252. $query->setParameter('sessionId', $sessionId);
  253. $query->execute();
  254. }
  255. $query = $this->db->getQueryBuilder();
  256. $query->update('spreedme_room_participants')
  257. ->set('sessionId', $query->createNamedParameter('0'))
  258. ->where($query->expr()->neq('roomId', $query->createNamedParameter($this->getId(), IQueryBuilder::PARAM_INT)))
  259. ->andWhere($query->expr()->eq('userId', $query->createNamedParameter($userId)));
  260. $query->execute();
  261. return $sessionId;
  262. }
  263. /**
  264. * @return string
  265. */
  266. public function enterRoomAsGuest() {
  267. $sessionId = $this->secureRandom->generate(255);
  268. while (!$this->db->insertIfNotExist('*PREFIX*spreedme_room_participants', [
  269. 'userId' => '',
  270. 'roomId' => $this->getId(),
  271. 'lastPing' => 0,
  272. 'sessionId' => $sessionId,
  273. 'participantType' => Participant::GUEST,
  274. ], ['sessionId'])) {
  275. $sessionId = $this->secureRandom->generate(255);
  276. }
  277. return $sessionId;
  278. }
  279. /**
  280. * @param string $sessionId
  281. * @return bool
  282. */
  283. protected function isSessionUnique($sessionId) {
  284. $query = $this->db->getQueryBuilder();
  285. $query->selectAlias($query->createFunction('COUNT(*)'), 'num_sessions')
  286. ->from('spreedme_room_participants')
  287. ->where($query->expr()->eq('sessionId', $query->createNamedParameter($sessionId)));
  288. $result = $query->execute();
  289. $numSessions = (int) $result->fetchColumn();
  290. $result->closeCursor();
  291. return $numSessions === 1;
  292. }
  293. public function cleanGuestParticipants() {
  294. $query = $this->db->getQueryBuilder();
  295. $query->delete('spreedme_room_participants')
  296. ->where($query->expr()->eq('roomId', $query->createNamedParameter($this->getId(), IQueryBuilder::PARAM_INT)))
  297. ->andWhere($query->expr()->emptyString('userId'))
  298. ->andWhere($query->expr()->lte('lastPing', $query->createNamedParameter(time() - 30, IQueryBuilder::PARAM_INT)));
  299. $query->execute();
  300. }
  301. /**
  302. * @param int $lastPing When the last ping is older than the given timestamp, the user is ignored
  303. * @return array[] Array of users with [users => [userId => [lastPing, sessionId]], guests => [[lastPing, sessionId]]]
  304. */
  305. public function getParticipants($lastPing = 0) {
  306. $query = $this->db->getQueryBuilder();
  307. $query->select('*')
  308. ->from('spreedme_room_participants')
  309. ->where($query->expr()->eq('roomId', $query->createNamedParameter($this->getId(), IQueryBuilder::PARAM_INT)));
  310. if ($lastPing > 0) {
  311. $query->andWhere($query->expr()->gt('lastPing', $query->createNamedParameter($lastPing, IQueryBuilder::PARAM_INT)));
  312. }
  313. $result = $query->execute();
  314. $users = $guests = [];
  315. while ($row = $result->fetch()) {
  316. if ($row['userId'] !== '' && $row['userId'] !== null) {
  317. $users[$row['userId']] = [
  318. 'lastPing' => (int) $row['lastPing'],
  319. 'sessionId' => $row['sessionId'],
  320. 'participantType' => (int) $row['participantType'],
  321. ];
  322. } else {
  323. $guests[] = [
  324. 'lastPing' => (int) $row['lastPing'],
  325. 'sessionId' => $row['sessionId'],
  326. ];
  327. }
  328. }
  329. $result->closeCursor();
  330. return [
  331. 'users' => $users,
  332. 'guests' => $guests,
  333. ];
  334. }
  335. /**
  336. * @param int $lastPing When the last ping is older than the given timestamp, the user is ignored
  337. * @return int
  338. */
  339. public function getNumberOfParticipants($lastPing = 0) {
  340. $query = $this->db->getQueryBuilder();
  341. $query->selectAlias($query->createFunction('COUNT(*)'), 'num_participants')
  342. ->from('spreedme_room_participants')
  343. ->where($query->expr()->eq('roomId', $query->createNamedParameter($this->getId(), IQueryBuilder::PARAM_INT)));
  344. if ($lastPing > 0) {
  345. $query->andWhere($query->expr()->gt('lastPing', $query->createNamedParameter($lastPing, IQueryBuilder::PARAM_INT)));
  346. }
  347. $result = $query->execute();
  348. $row = $result->fetch();
  349. $result->closeCursor();
  350. return isset($row['num_participants']) ? (int) $row['num_participants'] : 0;
  351. }
  352. /**
  353. * @param string $participant
  354. * @param string $sessionId
  355. * @param int $timestamp
  356. */
  357. public function ping($participant, $sessionId, $timestamp) {
  358. $query = $this->db->getQueryBuilder();
  359. $query->update('spreedme_room_participants')
  360. ->set('lastPing', $query->createNamedParameter($timestamp, IQueryBuilder::PARAM_INT))
  361. ->where($query->expr()->eq('userId', $query->createNamedParameter((string) $participant)))
  362. ->andWhere($query->expr()->eq('sessionId', $query->createNamedParameter($sessionId)))
  363. ->andWhere($query->expr()->eq('roomId', $query->createNamedParameter($this->getId(), IQueryBuilder::PARAM_INT)));
  364. $query->execute();
  365. }
  366. }