UpdateCalendarResourcesRoomsBackgroundJob.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  1. <?php
  2. /**
  3. * @copyright 2018, Georg Ehrke <oc.list@georgehrke.com>
  4. *
  5. * @author Georg Ehrke <oc.list@georgehrke.com>
  6. *
  7. * @license GNU AGPL version 3 or any later version
  8. *
  9. * This program is free software: you can redistribute it and/or modify
  10. * it under the terms of the GNU Affero General Public License as
  11. * published by the Free Software Foundation, either version 3 of the
  12. * License, or (at your option) any later version.
  13. *
  14. * This program is distributed in the hope that it will be useful,
  15. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17. * GNU Affero General Public License for more details.
  18. *
  19. * You should have received a copy of the GNU Affero General Public License
  20. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  21. *
  22. */
  23. namespace OCA\DAV\BackgroundJob;
  24. use OC\BackgroundJob\TimedJob;
  25. use OCA\DAV\CalDAV\CalDavBackend;
  26. use OCP\Calendar\BackendTemporarilyUnavailableException;
  27. use OCP\Calendar\Resource\IManager as IResourceManager;
  28. use OCP\Calendar\Resource\IResource;
  29. use OCP\Calendar\Room\IManager as IRoomManager;
  30. use OCP\Calendar\Room\IRoom;
  31. use OCP\IDBConnection;
  32. class UpdateCalendarResourcesRoomsBackgroundJob extends TimedJob {
  33. /** @var IResourceManager */
  34. private $resourceManager;
  35. /** @var IRoomManager */
  36. private $roomManager;
  37. /** @var IDBConnection */
  38. private $db;
  39. /** @var CalDavBackend */
  40. private $calDavBackend;
  41. /** @var string */
  42. private $resourceDbTable;
  43. /** @var string */
  44. private $resourcePrincipalUri;
  45. /** @var string */
  46. private $roomDbTable;
  47. /** @var string */
  48. private $roomPrincipalUri;
  49. /**
  50. * UpdateCalendarResourcesRoomsBackgroundJob constructor.
  51. *
  52. * @param IResourceManager $resourceManager
  53. * @param IRoomManager $roomManager
  54. * @param IDBConnection $dbConnection
  55. * @param CalDavBackend $calDavBackend
  56. */
  57. public function __construct(IResourceManager $resourceManager, IRoomManager $roomManager,
  58. IDBConnection $dbConnection, CalDavBackend $calDavBackend) {
  59. $this->resourceManager = $resourceManager;
  60. $this->roomManager = $roomManager;
  61. $this->db = $dbConnection;
  62. $this->calDavBackend = $calDavBackend;
  63. $this->resourceDbTable = 'calendar_resources';
  64. $this->resourcePrincipalUri = 'principals/calendar-resources';
  65. $this->roomDbTable = 'calendar_rooms';
  66. $this->roomPrincipalUri = 'principals/calendar-rooms';
  67. // run once an hour
  68. $this->setInterval(60 * 60);
  69. }
  70. /**
  71. * @param $argument
  72. */
  73. public function run($argument) {
  74. $this->runResources();
  75. $this->runRooms();
  76. }
  77. /**
  78. * run timed job for resources
  79. */
  80. private function runResources() {
  81. $resourceBackends = $this->resourceManager->getBackends();
  82. $cachedResources = $this->getCached($this->resourceDbTable);
  83. $cachedResourceIds = $this->getCachedResourceIds($cachedResources);
  84. $remoteResourceIds = [];
  85. foreach($resourceBackends as $resourceBackend) {
  86. try {
  87. $remoteResourceIds[$resourceBackend->getBackendIdentifier()] =
  88. $resourceBackend->listAllResources();
  89. } catch(BackendTemporarilyUnavailableException $ex) {
  90. // If the backend is temporarily unavailable
  91. // ignore this backend in this execution
  92. unset($cachedResourceIds[$resourceBackend->getBackendIdentifier()]);
  93. }
  94. }
  95. $sortedResources = $this->sortByNewDeletedExisting($cachedResourceIds, $remoteResourceIds);
  96. foreach($sortedResources['new'] as $backendId => $newResources) {
  97. foreach ($newResources as $newResource) {
  98. $backend = $this->resourceManager->getBackend($backendId);
  99. if ($backend === null) {
  100. continue;
  101. }
  102. $resource = $backend->getResource($newResource);
  103. $this->addToCache($this->resourceDbTable, $resource);
  104. }
  105. }
  106. foreach($sortedResources['deleted'] as $backendId => $deletedResources) {
  107. foreach ($deletedResources as $deletedResource) {
  108. $this->deleteFromCache($this->resourceDbTable,
  109. $this->resourcePrincipalUri, $backendId, $deletedResource);
  110. }
  111. }
  112. foreach($sortedResources['edited'] as $backendId => $editedResources) {
  113. foreach ($editedResources as $editedResource) {
  114. $backend = $this->resourceManager->getBackend($backendId);
  115. if ($backend === null) {
  116. continue;
  117. }
  118. $resource = $backend->getResource($editedResource);
  119. $this->updateCache($this->resourceDbTable, $resource);
  120. }
  121. }
  122. }
  123. /**
  124. * run timed job for rooms
  125. */
  126. private function runRooms() {
  127. $roomBackends = $this->roomManager->getBackends();
  128. $cachedRooms = $this->getCached($this->roomDbTable);
  129. $cachedRoomIds = $this->getCachedRoomIds($cachedRooms);
  130. $remoteRoomIds = [];
  131. foreach($roomBackends as $roomBackend) {
  132. try {
  133. $remoteRoomIds[$roomBackend->getBackendIdentifier()] =
  134. $roomBackend->listAllRooms();
  135. } catch(BackendTemporarilyUnavailableException $ex) {
  136. // If the backend is temporarily unavailable
  137. // ignore this backend in this execution
  138. unset($cachedRoomIds[$roomBackend->getBackendIdentifier()]);
  139. }
  140. }
  141. $sortedRooms = $this->sortByNewDeletedExisting($cachedRoomIds, $remoteRoomIds);
  142. foreach($sortedRooms['new'] as $backendId => $newRooms) {
  143. foreach ($newRooms as $newRoom) {
  144. $backend = $this->roomManager->getBackend($backendId);
  145. if ($backend === null) {
  146. continue;
  147. }
  148. $resource = $backend->getRoom($newRoom);
  149. $this->addToCache($this->roomDbTable, $resource);
  150. }
  151. }
  152. foreach($sortedRooms['deleted'] as $backendId => $deletedRooms) {
  153. foreach ($deletedRooms as $deletedRoom) {
  154. $this->deleteFromCache($this->roomDbTable,
  155. $this->roomPrincipalUri, $backendId, $deletedRoom);
  156. }
  157. }
  158. foreach($sortedRooms['edited'] as $backendId => $editedRooms) {
  159. foreach ($editedRooms as $editedRoom) {
  160. $backend = $this->roomManager->getBackend($backendId);
  161. if ($backend === null) {
  162. continue;
  163. }
  164. $resource = $backend->getRoom($editedRoom);
  165. $this->updateCache($this->roomDbTable, $resource);
  166. }
  167. }
  168. }
  169. /**
  170. * get cached db rows for resources / rooms
  171. * @param string $tableName
  172. * @return array
  173. */
  174. private function getCached($tableName):array {
  175. $query = $this->db->getQueryBuilder();
  176. $query->select('*')->from($tableName);
  177. $rows = [];
  178. $stmt = $query->execute();
  179. while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
  180. $rows[] = $row;
  181. }
  182. return $rows;
  183. }
  184. /**
  185. * @param array $cachedResources
  186. * @return array
  187. */
  188. private function getCachedResourceIds(array $cachedResources):array {
  189. $cachedResourceIds = [];
  190. foreach ($cachedResources as $cachedResource) {
  191. if (!isset($cachedResourceIds[$cachedResource['backend_id']])) {
  192. $cachedResourceIds[$cachedResource['backend_id']] = [];
  193. }
  194. $cachedResourceIds[$cachedResource['backend_id']][] =
  195. $cachedResource['resource_id'];
  196. }
  197. return $cachedResourceIds;
  198. }
  199. /**
  200. * @param array $cachedRooms
  201. * @return array
  202. */
  203. private function getCachedRoomIds(array $cachedRooms):array {
  204. $cachedRoomIds = [];
  205. foreach ($cachedRooms as $cachedRoom) {
  206. if (!isset($cachedRoomIds[$cachedRoom['backend_id']])) {
  207. $cachedRoomIds[$cachedRoom['backend_id']] = [];
  208. }
  209. $cachedRoomIds[$cachedRoom['backend_id']][] =
  210. $cachedRoom['resource_id'];
  211. }
  212. return $cachedRoomIds;
  213. }
  214. /**
  215. * sort list of ids by whether they appear only in the backend /
  216. * only in the cache / in both
  217. *
  218. * @param array $cached
  219. * @param array $remote
  220. * @return array
  221. */
  222. private function sortByNewDeletedExisting(array $cached, array $remote):array {
  223. $sorted = [
  224. 'new' => [],
  225. 'deleted' => [],
  226. 'edited' => [],
  227. ];
  228. $backendIds = array_merge(array_keys($cached), array_keys($remote));
  229. foreach($backendIds as $backendId) {
  230. if (!isset($cached[$backendId])) {
  231. $sorted['new'][$backendId] = $remote[$backendId];
  232. } elseif (!isset($remote[$backendId])) {
  233. $sorted['deleted'][$backendId] = $cached[$backendId];
  234. } else {
  235. $sorted['new'][$backendId] = array_diff($remote[$backendId], $cached[$backendId]);
  236. $sorted['deleted'][$backendId] = array_diff($cached[$backendId], $remote[$backendId]);
  237. $sorted['edited'][$backendId] = array_intersect($remote[$backendId], $cached[$backendId]);
  238. }
  239. }
  240. return $sorted;
  241. }
  242. /**
  243. * add entry to cache that exists remotely but not yet in cache
  244. *
  245. * @param string $table
  246. * @param IResource|IRoom $remote
  247. */
  248. private function addToCache($table, $remote) {
  249. $query = $this->db->getQueryBuilder();
  250. $query->insert($table)
  251. ->values([
  252. 'backend_id' => $query->createNamedParameter($remote->getBackend()->getBackendIdentifier()),
  253. 'resource_id' => $query->createNamedParameter($remote->getId()),
  254. 'email' => $query->createNamedParameter($remote->getEMail()),
  255. 'displayname' => $query->createNamedParameter($remote->getDisplayName()),
  256. 'group_restrictions' => $query->createNamedParameter(
  257. $this->serializeGroupRestrictions(
  258. $remote->getGroupRestrictions()
  259. ))
  260. ])
  261. ->execute();
  262. }
  263. /**
  264. * delete entry from cache that does not exist anymore remotely
  265. *
  266. * @param string $table
  267. * @param string $principalUri
  268. * @param string $backendId
  269. * @param string $resourceId
  270. */
  271. private function deleteFromCache($table, $principalUri, $backendId, $resourceId) {
  272. $query = $this->db->getQueryBuilder();
  273. $query->delete($table)
  274. ->where($query->expr()->eq('backend_id', $query->createNamedParameter($backendId)))
  275. ->andWhere($query->expr()->eq('resource_id', $query->createNamedParameter($resourceId)))
  276. ->execute();
  277. $calendar = $this->calDavBackend->getCalendarByUri($principalUri, implode('-', [$backendId, $resourceId]));
  278. if ($calendar !== null) {
  279. $this->calDavBackend->deleteCalendar($calendar['id']);
  280. }
  281. }
  282. /**
  283. * update an existing entry in cache
  284. *
  285. * @param string $table
  286. * @param IResource|IRoom $remote
  287. */
  288. private function updateCache($table, $remote) {
  289. $query = $this->db->getQueryBuilder();
  290. $query->update($table)
  291. ->set('email', $query->createNamedParameter($remote->getEMail()))
  292. ->set('displayname', $query->createNamedParameter($remote->getDisplayName()))
  293. ->set('group_restrictions', $query->createNamedParameter(
  294. $this->serializeGroupRestrictions(
  295. $remote->getGroupRestrictions()
  296. )))
  297. ->where($query->expr()->eq('backend_id', $query->createNamedParameter($remote->getBackend()->getBackendIdentifier())))
  298. ->andWhere($query->expr()->eq('resource_id', $query->createNamedParameter($remote->getId())))
  299. ->execute();
  300. }
  301. /**
  302. * serialize array of group restrictions to store them in database
  303. *
  304. * @param array $groups
  305. * @return string
  306. */
  307. private function serializeGroupRestrictions(array $groups):string {
  308. return \json_encode($groups);
  309. }
  310. }