diff options
author | John Molakvoæ <skjnldsv@users.noreply.github.com> | 2020-09-11 12:28:56 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-09-11 12:28:56 +0200 |
commit | 96f05d6a5cf3e34b572f26f3320d9b54e36b7f24 (patch) | |
tree | 9298ac252afb739b34a14a3d5e876c755f4bba5c /lib | |
parent | ce8992545a4bc9b63fb10a69380b5457880002cc (diff) | |
parent | c03bd908b6df2018b54fc6cdbdd90dca922e1c10 (diff) |
Merge pull request #1745 from call-me-matt/enh/social-chunks
Diffstat (limited to 'lib')
-rw-r--r-- | lib/Cron/SocialUpdate.php | 35 | ||||
-rw-r--r-- | lib/Cron/SocialUpdateRegistration.php | 4 | ||||
-rw-r--r-- | lib/Service/SocialApiService.php | 135 |
3 files changed, 148 insertions, 26 deletions
diff --git a/lib/Cron/SocialUpdate.php b/lib/Cron/SocialUpdate.php index fe245cdc..fcb41a5c 100644 --- a/lib/Cron/SocialUpdate.php +++ b/lib/Cron/SocialUpdate.php @@ -25,18 +25,49 @@ namespace OCA\Contacts\Cron; use OCA\Contacts\Service\SocialApiService; +use OCP\AppFramework\Http; +use OCP\BackgroundJob\IJobList; + class SocialUpdate extends \OC\BackgroundJob\QueuedJob { /** @var SocialUpdateService */ private $social; + /** @var IJobList */ + private $jobList; - public function __construct(SocialApiService $social) { + public function __construct(SocialApiService $social, + IJobList $jobList) { $this->social = $social; + $this->jobList = $jobList; } protected function run($arguments) { $userId = $arguments['userId']; + $offsetBook = $arguments['offsetBook'] ?? null; + $offsetContact = $arguments['offsetContact'] ?? null; // update contacts with first available social media profile - $this->social->updateAddressbooks('any', $userId); + $result = $this->social->updateAddressbooks('any', $userId, $offsetBook, $offsetContact); + + if ($result->getStatus() === Http::STATUS_PARTIAL_CONTENT) { + // not finished; schedule a follow-up + $report = $result->getData(); + $stoppedAtBook = $report[0]['stoppedAt']['addressBook']; + $stoppedAtContact = $report[0]['stoppedAt']['contact']; + + // make sure the offset contact/address book are still existing + if ($this->social->existsAddressBook($stoppedAtBook, $userId) == false) { + $stoppedAtBook = null; + } + if ($this->social->existsContact($stoppedAtContact, $stoppedAtBook, $userId) == false) { + $stoppedAtContact = null; + } + // TODO: can we check the userId still exists? + + $this->jobList->add(self::class, [ + 'userId' => $userId, + 'offsetBook' => $stoppedAtBook, + 'offsetContact' => $stoppedAtContact + ]); + } } } diff --git a/lib/Cron/SocialUpdateRegistration.php b/lib/Cron/SocialUpdateRegistration.php index 58c7a51a..1f4e1497 100644 --- a/lib/Cron/SocialUpdateRegistration.php +++ b/lib/Cron/SocialUpdateRegistration.php @@ -85,7 +85,9 @@ class SocialUpdateRegistration extends \OC\BackgroundJob\TimedJob { $bgSyncEnabledByUser = $this->config->getUserValue($user->getUID(), $this->appName, 'enableSocialSync', 'no'); if ($bgSyncEnabledByUser === 'yes') { $this->jobList->add(SocialUpdate::class, [ - 'userId' => $user->getUID() + 'userId' => $user->getUID(), + 'offsetBook' => null, + 'offsetContact' => null ]); } }); diff --git a/lib/Service/SocialApiService.php b/lib/Service/SocialApiService.php index f0339c07..8594ba68 100644 --- a/lib/Service/SocialApiService.php +++ b/lib/Service/SocialApiService.php @@ -38,6 +38,7 @@ use OCA\DAV\CardDAV\CardDavBackend; use OCA\DAV\CardDAV\ContactsManager; use OCP\IURLGenerator; use OCP\IL10N; +use OCP\AppFramework\Utility\ITimeFactory; class SocialApiService { private $appName; @@ -55,6 +56,8 @@ class SocialApiService { private $urlGen; /** @var CardDavBackend */ private $davBackend; + /** @var ITimeFactory */ + private $timeFactory; public function __construct( @@ -64,7 +67,8 @@ class SocialApiService { IClientService $clientService, IL10N $l10n, IURLGenerator $urlGen, - CardDavBackend $davBackend) { + CardDavBackend $davBackend, + ITimeFactory $timeFactory) { $this->appName = Application::APP_ID; $this->socialProvider = $socialProvider; $this->manager = $manager; @@ -73,12 +77,11 @@ class SocialApiService { $this->l10n = $l10n; $this->urlGen = $urlGen; $this->davBackend = $davBackend; + $this->timeFactory = $timeFactory; } /** - * @NoAdminRequired - * * returns an array of supported social networks * * @returns {array} array of the supported social networks @@ -93,8 +96,6 @@ class SocialApiService { /** - * @NoAdminRequired - * * Adds/updates photo for contact * * @param {pointer} contact reference to the contact to update @@ -123,17 +124,19 @@ class SocialApiService { /** - * @NoAdminRequired - * * Gets the addressbook of an addressbookId * * @param {String} addressbookId the identifier of the addressbook + * @param {IManager} manager optional a ContactManager to use * * @returns {IAddressBook} the corresponding addressbook or null */ - protected function getAddressBook(string $addressbookId) : ?IAddressBook { + protected function getAddressBook(string $addressbookId, IManager $manager = null) : ?IAddressBook { $addressBook = null; - $addressBooks = $this->manager->getUserAddressBooks(); + if ($manager === null) { + $manager = $this->manager; + } + $addressBooks = $manager->getUserAddressBooks(); foreach ($addressBooks as $ab) { if ($ab->getUri() === $addressbookId) { $addressBook = $ab; @@ -144,8 +147,6 @@ class SocialApiService { /** - * @NoAdminRequired - * * Retrieves and initiates all addressbooks from a user * * @param {string} userId the user to query @@ -158,8 +159,6 @@ class SocialApiService { } /** - * @NoAdminRequired - * * Retrieves social profile data for a contact and updates the entry * * @param {String} addressbookId the addressbook identifier @@ -217,8 +216,45 @@ class SocialApiService { } /** - * @NoAdminRequired + * checks an addressbook is existing + * + * @param {string} searchBookId the UID of the addressbook to verify + * @param {string} userId the user that should have access * + * @returns {bool} true if the addressbook exists + */ + public function existsAddressBook(string $searchBookId, string $userId): bool { + $manager = $this->manager; + $coma = new ContactsManager($this->davBackend, $this->l10n); + $coma->setupContactsProvider($manager, $userId, $this->urlGen); + $addressBooks = $manager->getUserAddressBooks(); + return $this->getAddressBook($searchBookId, $manager) !== null; + } + + /** + * checks a contact exists in an addressbook + * + * @param string searchContactId the UID of the contact to verify + * @param string searchBookId the UID of the addressbook to look in + * @param string userId the user that should have access + * + * @returns bool true if the contact exists + */ + public function existsContact(string $searchContactId, string $searchBookId, string $userId): bool { + // load address books for the user + $manager = $this->manager; + $coma = new ContactsManager($this->davBackend, $this->l10n); + $coma->setupContactsProvider($manager, $userId, $this->urlGen); + $addressBook = $this->getAddressBook($searchBookId, $manager); + if ($addressBook == null) { + return false; + } + + $check = $addressBook->search($searchContactId, ['UID'], ['types' => true]); + return !empty($check); + } + + /** * Stores the result of social avatar updates for each contact * (used during batch updates in updateAddressbooks) * @@ -254,10 +290,31 @@ class SocialApiService { return $report; } + /** + * sorts an array of address books + * + * @param {IAddressBook} a + * @param {IAddressBook} b + * + * @returns {bool} comparison by URI + */ + protected function sortAddressBooks(IAddressBook $a, IAddressBook $b) { + return strcmp($a->getURI(), $b->getURI()); + } /** - * @NoAdminRequired + * sorts an array of contacts * + * @param {array} a + * @param {array} b + * + * @returns {bool} comparison by UID + */ + protected function sortContacts(array $a, array $b) { + return strcmp($a['UID'], $b['UID']); + } + + /** * Updates social profile data for all contacts of an addressbook * * @param {String} network the social network to use (fallback: take first match) @@ -265,7 +322,7 @@ class SocialApiService { * * @returns {JSONResponse} JSONResponse with the list of changed and failed contacts */ - public function updateAddressbooks(string $network, string $userId) : JSONResponse { + public function updateAddressbooks(string $network, string $userId, string $offsetBook = null, string $offsetContact = null) : JSONResponse { // double check! $syncAllowedByAdmin = $this->config->getAppValue($this->appName, 'allowSocialSync', 'yes'); @@ -276,10 +333,12 @@ class SocialApiService { $delay = 1; $response = []; + $startTime = $this->timeFactory->getTime(); // get corresponding addressbook $this->registerAddressbooks($userId, $this->manager); $addressBooks = $this->manager->getUserAddressBooks(); + usort($addressBooks, [$this, 'sortAddressBooks']); // make sure the order stays the same in consecutive calls foreach ($addressBooks as $addressBook) { if ((is_null($addressBook) || @@ -290,18 +349,36 @@ class SocialApiService { continue; } + // in case this is a follow-up, jump to the last stopped address book + if (!is_null($offsetBook)) { + if ($addressBook->getURI() !== $offsetBook) { + continue; + } + $offsetBook = null; + } + // get contacts in that addressbook + //TODO: activate this optimization when nextcloud/server#22085 is merged + /* + if (Util::getVersion()[0] < 21) { + //TODO: remove this branch when dependency for contacts is min NCv21 (see info.xml) + $contacts = $addressBook->search('', ['UID'], ['types' => true]); + } else { + $contacts = $addressBook->search('', ['X-SOCIALPROFILE'], ['types' => true]); + } + */ $contacts = $addressBook->search('', ['UID'], ['types' => true]); - // TODO: can be optimized by: - // $contacts = $addressBook->search('', ['X-SOCIALPROFILE'], ['types' => true]); - // but see https://github.com/nextcloud/contacts/pull/1722#discussion_r463782429 - // and the referenced PR before activating this (index has to be re-created!) + usort($contacts, [$this, 'sortContacts']); // make sure the order stays the same in consecutive calls // update one contact after another foreach ($contacts as $contact) { - // delay to prevent rate limiting issues - // TODO: do we need to send an Http::STATUS_PROCESSING ? - sleep($delay); + // in case this is a follow-up, jump to the last stopped contact + if (!is_null($offsetContact)) { + if ($contact['UID'] !== $offsetContact) { + continue; + } + $offsetContact = null; + } try { $r = $this->updateContact($addressBook->getURI(), $contact['UID'], $network); @@ -309,6 +386,18 @@ class SocialApiService { } catch (Exception $e) { $response = $this->registerUpdateResult($response, $contact['FN'], '-1'); } + + // stop after 15sec (to be continued with next chunk) + if (($this->timeFactory->getTime() - $startTime) > 15) { + $response['stoppedAt'] = [ + 'addressBook' => $addressBook->getURI(), + 'contact' => $contact['UID'], + ]; + return new JSONResponse([$response], Http::STATUS_PARTIAL_CONTENT); + } + + // delay to prevent rate limiting issues + sleep($delay); } } return new JSONResponse([$response], Http::STATUS_OK); |