refactor: replace custom CRM with Monica fork
Some checks failed
Build & Push Monica Image to Gitea Registry / build-and-push (push) Failing after 9s
Some checks failed
Build & Push Monica Image to Gitea Registry / build-and-push (push) Failing after 9s
This commit is contained in:
245
app/Services/DavClient/Utils/AddressBookSynchronizer.php
Normal file
245
app/Services/DavClient/Utils/AddressBookSynchronizer.php
Normal file
@@ -0,0 +1,245 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\DavClient\Utils;
|
||||
|
||||
use Illuminate\Bus\Batch;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Bus;
|
||||
use App\Services\DavClient\Utils\Model\SyncDto;
|
||||
use App\Services\DavClient\Utils\Model\ContactDto;
|
||||
use App\Services\DavClient\Utils\Traits\WithSyncDto;
|
||||
use App\Services\DavClient\Utils\Traits\HasCapability;
|
||||
use App\Services\DavClient\Utils\Model\ContactDeleteDto;
|
||||
use App\Services\DavClient\UpdateSubscriptionLocalSyncToken;
|
||||
|
||||
class AddressBookSynchronizer
|
||||
{
|
||||
use HasCapability, WithSyncDto;
|
||||
|
||||
/**
|
||||
* Sync the address book.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function execute(SyncDto $sync, bool $force = false)
|
||||
{
|
||||
$this->sync = $sync;
|
||||
|
||||
$force
|
||||
? $this->forcesync()
|
||||
: $this->sync();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync the address book.
|
||||
*/
|
||||
private function sync()
|
||||
{
|
||||
// Get changes to sync
|
||||
$localChanges = $this->backend()->getChangesForAddressBook($this->sync->addressBookName(), (string) $this->sync->subscription->localSyncToken, 1);
|
||||
|
||||
// Get distant changes to sync
|
||||
$changes = $this->getDistantChanges();
|
||||
|
||||
// Get distant contacts
|
||||
$batch = app(AddressBookContactsUpdater::class)
|
||||
->execute($this->sync, $changes);
|
||||
|
||||
if (! $this->sync->subscription->readonly) {
|
||||
$batch->union(
|
||||
app(AddressBookContactsPush::class)
|
||||
->execute($this->sync, $changes, $localChanges)
|
||||
);
|
||||
}
|
||||
|
||||
$accountId = $this->sync->subscription->account_id;
|
||||
$subscriptionId = $this->sync->subscription->id;
|
||||
Bus::batch($batch)
|
||||
->then(function (Batch $batch) use ($accountId, $subscriptionId) {
|
||||
app(UpdateSubscriptionLocalSyncToken::class)->execute([
|
||||
'account_id' => $accountId,
|
||||
'addressbook_subscription_id' => $subscriptionId,
|
||||
]);
|
||||
})
|
||||
->allowFailures()
|
||||
->dispatch();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync the address book.
|
||||
*/
|
||||
private function forcesync()
|
||||
{
|
||||
$backend = $this->backend();
|
||||
|
||||
// Get changes to sync
|
||||
$localChanges = $backend->getChangesForAddressBook($this->sync->addressBookName(), (string) $this->sync->subscription->localSyncToken, 1);
|
||||
|
||||
// Get current list of contacts
|
||||
$localContacts = $backend->getObjects($this->sync->addressBookName());
|
||||
|
||||
// Get distant changes to sync
|
||||
$distContacts = $this->getAllContactsEtag();
|
||||
|
||||
// Get missed contacts
|
||||
$batch = app(AddressBookContactsUpdaterMissed::class)
|
||||
->execute($this->sync, $localContacts, $distContacts);
|
||||
|
||||
if (! $this->sync->subscription->readonly) {
|
||||
$batch->union(
|
||||
app(AddressBookContactsPushMissed::class)
|
||||
->execute($this->sync, $localChanges, $distContacts, $localContacts)
|
||||
);
|
||||
}
|
||||
|
||||
$accountId = $this->sync->subscription->account_id;
|
||||
$subscriptionId = $this->sync->subscription->id;
|
||||
Bus::batch($batch)
|
||||
->then(function (Batch $batch) use ($accountId, $subscriptionId) {
|
||||
app(UpdateSubscriptionLocalSyncToken::class)->execute([
|
||||
'account_id' => $accountId,
|
||||
'addressbook_subscription_id' => $subscriptionId,
|
||||
]);
|
||||
})
|
||||
->allowFailures()
|
||||
->dispatch();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get distant changes to sync.
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
private function getDistantChanges(): Collection
|
||||
{
|
||||
$etags = collect($this->getDistantEtags());
|
||||
$contacts = $etags->filter(function ($contact, $href): bool {
|
||||
return $this->filterDistantContacts($contact, $href);
|
||||
})
|
||||
->map(function (array $contact, string $href): ContactDto {
|
||||
return new ContactDto($href, Arr::get($contact, 'properties.200.{DAV:}getetag'));
|
||||
});
|
||||
|
||||
$deleted = $etags->filter(function ($contact): bool {
|
||||
return is_array($contact) && $contact['status'] === '404';
|
||||
})
|
||||
->map(function (array $contact, string $href): ContactDto {
|
||||
return new ContactDeleteDto($href);
|
||||
});
|
||||
|
||||
return $contacts->union($deleted);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter contacts to only return vcards type and new contacts or contacts with matching etags.
|
||||
*
|
||||
* @param mixed $contact
|
||||
* @param string $href
|
||||
* @return bool
|
||||
*/
|
||||
private function filterDistantContacts($contact, $href): bool
|
||||
{
|
||||
// only return vcards
|
||||
if (! is_array($contact) || ! Str::contains(Arr::get($contact, 'properties.200.{DAV:}getcontenttype'), 'text/vcard')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// only new contact or contact with etag that match
|
||||
$card = $this->backend()->getCard($this->sync->addressBookName(), $href);
|
||||
|
||||
return $card === false || $card['etag'] !== Arr::get($contact, 'properties.200.{DAV:}getetag');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get refreshed etags.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getDistantEtags(): array
|
||||
{
|
||||
if ($this->hasCapability('syncCollection')) {
|
||||
// With sync-collection
|
||||
return $this->callSyncCollectionWhenNeeded();
|
||||
} else {
|
||||
// With PROPFIND
|
||||
return $this->sync->propFind([
|
||||
'{DAV:}getcontenttype',
|
||||
'{DAV:}getetag',
|
||||
], 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sync-collection request if sync-token has changed.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function callSyncCollectionWhenNeeded(): array
|
||||
{
|
||||
// get the current distant syncToken
|
||||
$distantSyncToken = $this->sync->getProperty('{DAV:}sync-token');
|
||||
|
||||
if (($this->sync->subscription->syncToken ?? '') === $distantSyncToken) {
|
||||
// no change at all
|
||||
return [];
|
||||
}
|
||||
|
||||
return $this->callSyncCollection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sync-collection request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function callSyncCollection(): array
|
||||
{
|
||||
$syncToken = $this->sync->subscription->syncToken ?? '';
|
||||
|
||||
// get sync
|
||||
$collection = $this->sync->syncCollection([
|
||||
'{DAV:}getcontenttype',
|
||||
'{DAV:}getetag',
|
||||
], $syncToken);
|
||||
|
||||
// save the new syncToken as current one
|
||||
if ($newSyncToken = Arr::get($collection, 'synctoken')) {
|
||||
$this->sync->subscription->syncToken = $newSyncToken;
|
||||
$this->sync->subscription->save();
|
||||
}
|
||||
|
||||
return $collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all contacts etag.
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
private function getAllContactsEtag(): Collection
|
||||
{
|
||||
if (! $this->hasCapability('addressbookQuery')) {
|
||||
return collect();
|
||||
}
|
||||
|
||||
$data = $this->sync->addressbookQuery('{DAV:}getetag');
|
||||
$data = collect($data);
|
||||
|
||||
$updated = $data->filter(function ($contact): bool {
|
||||
return is_array($contact) && $contact['status'] === '200';
|
||||
})
|
||||
->map(function (array $contact, string $href): ContactDto {
|
||||
return new ContactDto($href, Arr::get($contact, 'properties.200.{DAV:}getetag'));
|
||||
});
|
||||
$deleted = $data->filter(function ($contact): bool {
|
||||
return is_array($contact) && $contact['status'] === '404';
|
||||
})
|
||||
->map(function (array $contact, string $href): ContactDto {
|
||||
return new ContactDeleteDto($href);
|
||||
});
|
||||
|
||||
return $updated->union($deleted);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user