app/Http/Controllers/DAV/Backend/SyncDAVBackend.php (108 lines of code) (raw):

<?php namespace App\Http\Controllers\DAV\Backend; use Illuminate\Support\Str; use App\Models\User\SyncToken; use Illuminate\Support\Facades\Auth; trait SyncDAVBackend { /** * This method returns a sync-token for this collection. * * If null is returned from this function, the plugin assumes there's no * sync information available. * * @return SyncToken|null */ protected function getCurrentSyncToken(): ?SyncToken { $tokens = SyncToken::where([ 'account_id' => Auth::user()->account_id, 'user_id' => Auth::user()->id, 'name' => $this->backendUri(), ]) ->orderBy('created_at') ->get(); return $tokens->count() > 0 ? $tokens->last() : null; } /** * Create or refresh the token if a change happened. * * @return SyncToken */ public function refreshSyncToken(): SyncToken { $token = $this->getCurrentSyncToken(); if (! $token || $token->timestamp < $this->getLastModified()) { $token = $this->createSyncTokenNow(); } return $token; } /** * Get SyncToken by token id. * * @return SyncToken|null */ protected function getSyncToken($syncToken) { return SyncToken::where([ 'account_id' => Auth::user()->account_id, 'user_id' => Auth::user()->id, 'name' => $this->backendUri(), ]) ->find($syncToken); } /** * Create a token with now timestamp. * * @return SyncToken */ private function createSyncTokenNow() { return SyncToken::create([ 'account_id' => Auth::user()->account_id, 'user_id' => Auth::user()->id, 'name' => $this->backendUri(), 'timestamp' => now(), ]); } /** * Returns the last modification date. * * @return \Carbon\Carbon|null */ public function getLastModified() { return $this->getObjects() ->max('updated_at'); } /** * The getChanges method returns all the changes that have happened, since * the specified syncToken. * * This function should return an array, such as the following: * * [ * 'syncToken' => 'The current synctoken', * 'added' => [ * 'new.txt', * ], * 'modified' => [ * 'modified.txt', * ], * 'deleted' => [ * 'foo.php.bak', * 'old.txt' * ] * ); * * The returned syncToken property should reflect the *current* syncToken * , as reported in the {http://sabredav.org/ns}sync-token * property This is * needed here too, to ensure the operation is atomic. * * If the $syncToken argument is specified as null, this is an initial * sync, and all members should be reported. * * The modified property is an array of nodenames that have changed since * the last token. * * The deleted property is an array with nodenames, that have been deleted * from collection. * * The $syncLevel argument is basically the 'depth' of the report. If it's * 1, you only have to report changes that happened only directly in * immediate descendants. If it's 2, it should also include changes from * the nodes below the child collections. (grandchildren) * * The $limit argument allows a client to specify how many results should * be returned at most. If the limit is not specified, it should be treated * as infinite. * * If the limit (infinite or not) is higher than you're willing to return, * you should throw a Sabre\DAV\Exception\TooMuchMatches() exception. * * If the syncToken is expired (due to data cleanup) or unknown, you must * return null. * * The limit is 'suggestive'. You are free to ignore it. * * @param string $syncToken * @return array|null */ public function getChanges($syncToken): ?array { $token = null; $timestamp = null; if (! empty($syncToken)) { $token = $this->getSyncToken($syncToken); if (is_null($token)) { // syncToken is not recognized return null; } $timestamp = $token->timestamp; } $objs = $this->getObjects(); $modified = $objs->filter(function ($obj) use ($timestamp) { return ! is_null($timestamp) && $obj->updated_at > $timestamp && $obj->created_at < $timestamp; }); $added = $objs->filter(function ($obj) use ($timestamp) { return is_null($timestamp) || $obj->created_at >= $timestamp; }); return [ 'syncToken' => $this->refreshSyncToken()->id, 'added' => $added->map(function ($obj) { return $this->encodeUri($obj); })->values()->toArray(), 'modified' => $modified->map(function ($obj) { return $this->encodeUri($obj); })->values()->toArray(), 'deleted' => [], ]; } protected function encodeUri($obj) { if (empty($obj->uuid)) { // refresh model from database $obj->refresh(); if (empty($obj->uuid)) { // in case uuid is still not set, do it $obj->forceFill([ 'uuid' => Str::uuid(), ])->save(); } } return urlencode($obj->uuid.$this->getExtension()); } private function decodeUri($uri) { return pathinfo(urldecode($uri), PATHINFO_FILENAME); } /** * Returns the contact for the specific uri. * * @param string $uri * @return mixed */ public function getObject($uri) { try { return $this->getObjectUuid($this->decodeUri($uri)); } catch (\Exception $e) { // Object not found } } /** * Returns the object for the specific uuid. * * @param string $uuid * @return mixed */ abstract public function getObjectUuid($uuid); /** * Returns the collection of objects. * * @return \Illuminate\Support\Collection */ abstract public function getObjects(); abstract public function getExtension(); }