Browse Source
cache webcal calendars on server
cache webcal calendars on server
Signed-off-by: Georg Ehrke <developer@georgehrke.com>pull/10059/head
No known key found for this signature in database
GPG Key ID: 9D98FD9380A1CB43
20 changed files with 2379 additions and 116 deletions
-
28apps/dav/appinfo/app.php
-
1apps/dav/appinfo/info.xml
-
6apps/dav/composer/composer/autoload_classmap.php
-
6apps/dav/composer/composer/autoload_static.php
-
438apps/dav/lib/BackgroundJob/RefreshWebcalJob.php
-
198apps/dav/lib/CalDAV/CachedSubscription.php
-
64apps/dav/lib/CalDAV/CachedSubscriptionObject.php
-
465apps/dav/lib/CalDAV/CalDavBackend.php
-
20apps/dav/lib/CalDAV/CalendarHome.php
-
145apps/dav/lib/CalDAV/WebcalCaching/Plugin.php
-
83apps/dav/lib/Migration/RefreshWebcalJobRegistrar.php
-
105apps/dav/lib/Migration/Version1006Date20180628111625.php
-
3apps/dav/lib/Server.php
-
242apps/dav/tests/unit/BackgroundJob/RefreshWebcalJobTest.php
-
15apps/dav/tests/unit/CalDAV/AbstractCalDavBackend.php
-
95apps/dav/tests/unit/CalDAV/CachedSubscriptionObjectTest.php
-
300apps/dav/tests/unit/CalDAV/CachedSubscriptionTest.php
-
72apps/dav/tests/unit/CalDAV/CalDavBackendTest.php
-
63apps/dav/tests/unit/CalDAV/WebcalCaching/PluginTest.php
-
146apps/dav/tests/unit/Migration/RefreshWebcalJobRegistrarTest.php
@ -0,0 +1,438 @@ |
|||
<?php |
|||
declare(strict_types=1); |
|||
/** |
|||
* @copyright 2018 Georg Ehrke <oc.list@georgehrke.com> |
|||
* |
|||
* @author Georg Ehrke <oc.list@georgehrke.com> |
|||
* |
|||
* @license GNU AGPL version 3 or any later version |
|||
* |
|||
* This program is free software: you can redistribute it and/or modify |
|||
* it under the terms of the GNU Affero General Public License as |
|||
* published by the Free Software Foundation, either version 3 of the |
|||
* License, or (at your option) any later version. |
|||
* |
|||
* This program is distributed in the hope that it will be useful, |
|||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
* GNU Affero General Public License for more details. |
|||
* |
|||
* You should have received a copy of the GNU Affero General Public License |
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
|||
* |
|||
*/ |
|||
namespace OCA\DAV\BackgroundJob; |
|||
|
|||
use GuzzleHttp\HandlerStack; |
|||
use GuzzleHttp\Middleware; |
|||
use OC\BackgroundJob\Job; |
|||
use OCA\DAV\CalDAV\CalDavBackend; |
|||
use OCP\AppFramework\Utility\ITimeFactory; |
|||
use OCP\Http\Client\IClientService; |
|||
use OCP\IConfig; |
|||
use OCP\ILogger; |
|||
use Psr\Http\Message\RequestInterface; |
|||
use Psr\Http\Message\ResponseInterface; |
|||
use Sabre\DAV\Exception\BadRequest; |
|||
use Sabre\DAV\PropPatch; |
|||
use Sabre\DAV\Xml\Property\Href; |
|||
use Sabre\VObject\Component; |
|||
use Sabre\VObject\DateTimeParser; |
|||
use Sabre\VObject\InvalidDataException; |
|||
use Sabre\VObject\ParseException; |
|||
use Sabre\VObject\Reader; |
|||
use Sabre\VObject\Splitter\ICalendar; |
|||
|
|||
class RefreshWebcalJob extends Job { |
|||
|
|||
/** @var CalDavBackend */ |
|||
private $calDavBackend; |
|||
|
|||
/** @var IClientService */ |
|||
private $clientService; |
|||
|
|||
/** @var IConfig */ |
|||
private $config; |
|||
|
|||
/** @var ILogger */ |
|||
private $logger; |
|||
|
|||
/** @var ITimeFactory */ |
|||
private $timeFactory; |
|||
|
|||
/** @var array */ |
|||
private $subscription; |
|||
|
|||
/** |
|||
* RefreshWebcalJob constructor. |
|||
* |
|||
* @param CalDavBackend $calDavBackend |
|||
* @param IClientService $clientService |
|||
* @param IConfig $config |
|||
* @param ILogger $logger |
|||
* @param ITimeFactory $timeFactory |
|||
*/ |
|||
public function __construct(CalDavBackend $calDavBackend, IClientService $clientService, IConfig $config, ILogger $logger, ITimeFactory $timeFactory) { |
|||
$this->calDavBackend = $calDavBackend; |
|||
$this->clientService = $clientService; |
|||
$this->config = $config; |
|||
$this->logger = $logger; |
|||
$this->timeFactory = $timeFactory; |
|||
} |
|||
|
|||
/** |
|||
* this function is called at most every hour |
|||
* |
|||
* @inheritdoc |
|||
*/ |
|||
public function execute($jobList, ILogger $logger = null) { |
|||
$subscription = $this->getSubscription($this->argument['principaluri'], $this->argument['uri']); |
|||
if (!$subscription) { |
|||
return; |
|||
} |
|||
|
|||
// if no refresh rate was configured, just refresh once a week
|
|||
$subscriptionId = $subscription['id']; |
|||
$refreshrate = $subscription['refreshrate'] ?? 'P1W'; |
|||
|
|||
try { |
|||
/** @var \DateInterval $dateInterval */ |
|||
$dateInterval = DateTimeParser::parseDuration($refreshrate); |
|||
} catch(InvalidDataException $ex) { |
|||
$this->logger->logException($ex); |
|||
$this->logger->warning("Subscription $subscriptionId could not be refreshed, refreshrate in database is invalid"); |
|||
return; |
|||
} |
|||
|
|||
$interval = $this->getIntervalFromDateInterval($dateInterval); |
|||
if (($this->timeFactory->getTime() - $this->lastRun) <= $interval) { |
|||
return; |
|||
} |
|||
|
|||
parent::execute($jobList, $logger); |
|||
} |
|||
|
|||
/** |
|||
* @param array $argument |
|||
*/ |
|||
protected function run($argument) { |
|||
$subscription = $this->getSubscription($argument['principaluri'], $argument['uri']); |
|||
$mutations = []; |
|||
if (!$subscription) { |
|||
return; |
|||
} |
|||
|
|||
$webcalData = $this->queryWebcalFeed($subscription, $mutations); |
|||
if (!$webcalData) { |
|||
return; |
|||
} |
|||
|
|||
$stripTodos = $subscription['striptodos'] ?? 1; |
|||
$stripAlarms = $subscription['stripalarms'] ?? 1; |
|||
$stripAttachments = $subscription['stripattachments'] ?? 1; |
|||
|
|||
try { |
|||
$splitter = new ICalendar($webcalData, Reader::OPTION_FORGIVING); |
|||
|
|||
// we wait with deleting all outdated events till we parsed the new ones
|
|||
// in case the new calendar is broken and `new ICalendar` throws a ParseException
|
|||
// the user will still see the old data
|
|||
$this->calDavBackend->purgeAllCachedEventsForSubscription($subscription['id']); |
|||
|
|||
while ($vObject = $splitter->getNext()) { |
|||
/** @var Component $vObject */ |
|||
$uid = null; |
|||
$compName = null; |
|||
|
|||
foreach ($vObject->getComponents() as $component) { |
|||
if ($component->name === 'VTIMEZONE') { |
|||
continue; |
|||
} |
|||
|
|||
$uid = $component->{'UID'}->getValue(); |
|||
$compName = $component->name; |
|||
|
|||
if ($stripAlarms) { |
|||
unset($component->{'VALARM'}); |
|||
} |
|||
if ($stripAttachments) { |
|||
unset($component->{'ATTACH'}); |
|||
} |
|||
} |
|||
|
|||
if ($stripTodos && $compName === 'VTODO') { |
|||
continue; |
|||
} |
|||
|
|||
$uri = $uid . '.ics'; |
|||
$calendarData = $vObject->serialize(); |
|||
try { |
|||
$this->calDavBackend->createCalendarObject($subscription['id'], $uri, $calendarData, CalDavBackend::CALENDAR_TYPE_SUBSCRIPTION); |
|||
} catch(BadRequest $ex) { |
|||
$this->logger->logException($ex); |
|||
} |
|||
} |
|||
|
|||
$newRefreshRate = $this->checkWebcalDataForRefreshRate($subscription, $webcalData); |
|||
if ($newRefreshRate) { |
|||
$mutations['{http://apple.com/ns/ical/}refreshrate'] = $newRefreshRate; |
|||
} |
|||
|
|||
$this->updateSubscription($subscription, $mutations); |
|||
} catch(ParseException $ex) { |
|||
$subscriptionId = $subscription['id']; |
|||
|
|||
$this->logger->logException($ex); |
|||
$this->logger->warning("Subscription $subscriptionId could not be refreshed due to a parsing error"); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* gets webcal feed from remote server |
|||
* |
|||
* @param array $subscription |
|||
* @param array &$mutations |
|||
* @return null|string |
|||
*/ |
|||
private function queryWebcalFeed(array $subscription, array &$mutations) { |
|||
$client = $this->clientService->newClient(); |
|||
|
|||
$didBreak301Chain = false; |
|||
$latestLocation = null; |
|||
|
|||
$handlerStack = HandlerStack::create(); |
|||
$handlerStack->push(Middleware::mapRequest(function (RequestInterface $request) { |
|||
return $request |
|||
->withHeader('Accept', 'text/calendar, application/calendar+json, application/calendar+xml') |
|||
->withHeader('User-Agent', 'Nextcloud Webcal Crawler'); |
|||
})); |
|||
$handlerStack->push(Middleware::mapResponse(function(ResponseInterface $response) use (&$didBreak301Chain, &$latestLocation) { |
|||
if (!$didBreak301Chain) { |
|||
if ($response->getStatusCode() !== 301) { |
|||
$didBreak301Chain = true; |
|||
} else { |
|||
$latestLocation = $response->getHeader('Location'); |
|||
} |
|||
} |
|||
return $response; |
|||
})); |
|||
|
|||
$allowLocalAccess = $this->config->getAppValue('dav', 'webcalAllowLocalAccess', 'no'); |
|||
$subscriptionId = $subscription['id']; |
|||
$url = $this->cleanURL($subscription['source']); |
|||
if ($url === null) { |
|||
return null; |
|||
} |
|||
|
|||
if ($allowLocalAccess !== 'yes') { |
|||
$host = parse_url($url, PHP_URL_HOST); |
|||
// remove brackets from IPv6 addresses
|
|||
if (strpos($host, '[') === 0 && substr($host, -1) === ']') { |
|||
$host = substr($host, 1, -1); |
|||
} |
|||
|
|||
if ($host === 'localhost' || substr($host, -6) === '.local' || substr($host, -10) === '.localhost' || |
|||
preg_match('/(^127\.)|(^192\.168\.)|(^10\.)|(^172\.1[6-9]\.)|(^172\.2[0-9]\.)|(^172\.3[0-1]\.)|(^::1$)|(^[fF][cCdD])/', $host)) { |
|||
$this->logger->warning("Subscription $subscriptionId was not refreshed because it violates local access rules"); |
|||
return null; |
|||
} |
|||
} |
|||
|
|||
try { |
|||
$params = [ |
|||
'allow_redirects' => [ |
|||
'redirects' => 10 |
|||
], |
|||
'handler' => $handlerStack, |
|||
]; |
|||
|
|||
$user = parse_url($subscription['source'], PHP_URL_USER); |
|||
$pass = parse_url($subscription['source'], PHP_URL_PASS); |
|||
if ($user !== null && $pass !== null) { |
|||
$params['auth'] = [$user, $pass]; |
|||
} |
|||
|
|||
$response = $client->get($url, $params); |
|||
$body = $response->getBody(); |
|||
|
|||
if ($latestLocation) { |
|||
$mutations['{http://calendarserver.org/ns/}source'] = new Href($latestLocation); |
|||
} |
|||
|
|||
$contentType = $response->getHeader('Content-Type'); |
|||
$contentType = explode(';', $contentType, 2)[0]; |
|||
switch($contentType) { |
|||
case 'application/calendar+json': |
|||
try { |
|||
$jCalendar = Reader::readJson($body, Reader::OPTION_FORGIVING); |
|||
} catch(\Exception $ex) { |
|||
// In case of a parsing error return null
|
|||
$this->logger->debug("Subscription $subscriptionId could not be parsed"); |
|||
return null; |
|||
} |
|||
return $jCalendar->serialize(); |
|||
|
|||
case 'application/calendar+xml': |
|||
try { |
|||
$xCalendar = Reader::readXML($body); |
|||
} catch(\Exception $ex) { |
|||
// In case of a parsing error return null
|
|||
$this->logger->debug("Subscription $subscriptionId could not be parsed"); |
|||
return null; |
|||
} |
|||
return $xCalendar->serialize(); |
|||
|
|||
case 'text/calendar': |
|||
default: |
|||
try { |
|||
$vCalendar = Reader::read($body); |
|||
} catch(\Exception $ex) { |
|||
// In case of a parsing error return null
|
|||
$this->logger->debug("Subscription $subscriptionId could not be parsed"); |
|||
return null; |
|||
} |
|||
return $vCalendar->serialize(); |
|||
} |
|||
} catch(\Exception $ex) { |
|||
$this->logger->logException($ex); |
|||
$this->logger->warning("Subscription $subscriptionId could not be refreshed due to a network error"); |
|||
|
|||
return null; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* loads subscription from backend |
|||
* |
|||
* @param string $principalUri |
|||
* @param string $uri |
|||
* @return array|null |
|||
*/ |
|||
private function getSubscription(string $principalUri, string $uri) { |
|||
$subscriptions = array_values(array_filter( |
|||
$this->calDavBackend->getSubscriptionsForUser($principalUri), |
|||
function($sub) use ($uri) { |
|||
return $sub['uri'] === $uri; |
|||
} |
|||
)); |
|||
|
|||
if (\count($subscriptions) === 0) { |
|||
return null; |
|||
} |
|||
|
|||
$this->subscription = $subscriptions[0]; |
|||
return $this->subscription; |
|||
} |
|||
|
|||
/** |
|||
* get total number of seconds from DateInterval object |
|||
* |
|||
* @param \DateInterval $interval |
|||
* @return int |
|||
*/ |
|||
private function getIntervalFromDateInterval(\DateInterval $interval):int { |
|||
return $interval->s |
|||
+ ($interval->i * 60) |
|||
+ ($interval->h * 60 * 60) |
|||
+ ($interval->d * 60 * 60 * 24) |
|||
+ ($interval->m * 60 * 60 * 24 * 30) |
|||
+ ($interval->y * 60 * 60 * 24 * 365); |
|||
} |
|||
|
|||
/** |
|||
* check if: |
|||
* - current subscription stores a refreshrate |
|||
* - the webcal feed suggests a refreshrate |
|||
* - return suggested refreshrate if user didn't set a custom one |
|||
* |
|||
* @param array $subscription |
|||
* @param string $webcalData |
|||
* @return string|null |
|||
*/ |
|||
private function checkWebcalDataForRefreshRate($subscription, $webcalData) { |
|||
// if there is no refreshrate stored in the database, check the webcal feed
|
|||
// whether it suggests any refresh rate and store that in the database
|
|||
if (isset($subscription['refreshrate']) && $subscription['refreshrate'] !== null) { |
|||
return null; |
|||
} |
|||
|
|||
/** @var Component\VCalendar $vCalendar */ |
|||
$vCalendar = Reader::read($webcalData); |
|||
|
|||
$newRefreshrate = null; |
|||
if (isset($vCalendar->{'X-PUBLISHED-TTL'})) { |
|||
$newRefreshrate = $vCalendar->{'X-PUBLISHED-TTL'}->getValue(); |
|||
} |
|||
if (isset($vCalendar->{'REFRESH-INTERVAL'})) { |
|||
$newRefreshrate = $vCalendar->{'REFRESH-INTERVAL'}->getValue(); |
|||
} |
|||
|
|||
if (!$newRefreshrate) { |
|||
return null; |
|||
} |
|||
|
|||
// check if new refresh rate is even valid
|
|||
try { |
|||
DateTimeParser::parseDuration($newRefreshrate); |
|||
} catch(InvalidDataException $ex) { |
|||
return null; |
|||
} |
|||
|
|||
return $newRefreshrate; |
|||
} |
|||
|
|||
/** |
|||
* update subscription stored in database |
|||
* used to set: |
|||
* - refreshrate |
|||
* - source |
|||
* |
|||
* @param array $subscription |
|||
* @param array $mutations |
|||
*/ |
|||
private function updateSubscription(array $subscription, array $mutations) { |
|||
if (empty($mutations)) { |
|||
return; |
|||
} |
|||
|
|||
$propPatch = new PropPatch($mutations); |
|||
$this->calDavBackend->updateSubscription($subscription['id'], $propPatch); |
|||
$propPatch->commit(); |
|||
} |
|||
|
|||
/** |
|||
* This method will strip authentication information and replace the |
|||
* 'webcal' or 'webcals' protocol scheme |
|||
* |
|||
* @param string $url |
|||
* @return string|null |
|||
*/ |
|||
private function cleanURL(string $url) { |
|||
$parsed = parse_url($url); |
|||
if ($parsed === false) { |
|||
return null; |
|||
} |
|||
|
|||
if (isset($parsed['scheme']) && $parsed['scheme'] === 'http') { |
|||
$scheme = 'http'; |
|||
} else { |
|||
$scheme = 'https'; |
|||
} |
|||
|
|||
$host = $parsed['host'] ?? ''; |
|||
$port = isset($parsed['port']) ? ':' . $parsed['port'] : ''; |
|||
$path = $parsed['path'] ?? ''; |
|||
$query = isset($parsed['query']) ? '?' . $parsed['query'] : ''; |
|||
$fragment = isset($parsed['fragment']) ? '#' . $parsed['fragment'] : ''; |
|||
|
|||
$cleanURL = "$scheme://$host$port$path$query$fragment"; |
|||
// parse_url is giving some weird results if no url and no :// is given,
|
|||
// so let's test the url again
|
|||
$parsedClean = parse_url($cleanURL); |
|||
if ($parsedClean === false || !isset($parsedClean['host'])) { |
|||
return null; |
|||
} |
|||
|
|||
return $cleanURL; |
|||
} |
|||
} |
|||
@ -0,0 +1,198 @@ |
|||
<?php |
|||
declare(strict_types=1); |
|||
/** |
|||
* @copyright 2018 Georg Ehrke <oc.list@georgehrke.com> |
|||
* |
|||
* @author Georg Ehrke <oc.list@georgehrke.com> |
|||
* |
|||
* @license GNU AGPL version 3 or any later version |
|||
* |
|||
* This program is free software: you can redistribute it and/or modify |
|||
* it under the terms of the GNU Affero General Public License as |
|||
* published by the Free Software Foundation, either version 3 of the |
|||
* License, or (at your option) any later version. |
|||
* |
|||
* This program is distributed in the hope that it will be useful, |
|||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
* GNU Affero General Public License for more details. |
|||
* |
|||
* You should have received a copy of the GNU Affero General Public License |
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
|||
* |
|||
*/ |
|||
namespace OCA\DAV\CalDAV; |
|||
|
|||
use Sabre\CalDAV\Backend\BackendInterface; |
|||
use Sabre\DAV\Exception\MethodNotAllowed; |
|||
use Sabre\DAV\Exception\NotFound; |
|||
use Sabre\DAV\PropPatch; |
|||
|
|||
/** |
|||
* Class CachedSubscription |
|||
* |
|||
* @package OCA\DAV\CalDAV |
|||
* @property BackendInterface|CalDavBackend $caldavBackend |
|||
*/ |
|||
class CachedSubscription extends \Sabre\CalDAV\Calendar { |
|||
|
|||
/** |
|||
* @return string |
|||
*/ |
|||
public function getPrincipalURI():string { |
|||
return $this->calendarInfo['principaluri']; |
|||
} |
|||
|
|||
/** |
|||
* @return array |
|||
*/ |
|||
public function getACL():array { |
|||
return [ |
|||
[ |
|||
'privilege' => '{DAV:}read', |
|||
'principal' => $this->getOwner(), |
|||
'protected' => true, |
|||
], |
|||
[ |
|||
'privilege' => '{DAV:}read', |
|||
'principal' => $this->getOwner() . '/calendar-proxy-write', |
|||
'protected' => true, |
|||
], |
|||
[ |
|||
'privilege' => '{DAV:}read', |
|||
'principal' => $this->getOwner() . '/calendar-proxy-read', |
|||
'protected' => true, |
|||
], |
|||
[ |
|||
'privilege' => '{' . Plugin::NS_CALDAV . '}read-free-busy', |
|||
'principal' => '{DAV:}authenticated', |
|||
'protected' => true, |
|||
], |
|||
]; |
|||
} |
|||
|
|||
/** |
|||
* @return array |
|||
*/ |
|||
public function getChildACL():array { |
|||
return [ |
|||
[ |
|||
'privilege' => '{DAV:}read', |
|||
'principal' => $this->getOwner(), |
|||
'protected' => true, |
|||
], |
|||
|
|||
[ |
|||
'privilege' => '{DAV:}read', |
|||
'principal' => $this->getOwner() . '/calendar-proxy-write', |
|||
'protected' => true, |
|||
], |
|||
[ |
|||
'privilege' => '{DAV:}read', |
|||
'principal' => $this->getOwner() . '/calendar-proxy-read', |
|||
'protected' => true, |
|||
], |
|||
|
|||
]; |
|||
} |
|||
|
|||
/** |
|||
* @return null|string |
|||
*/ |
|||
public function getOwner() { |
|||
if (isset($this->calendarInfo['{http://owncloud.org/ns}owner-principal'])) { |
|||
return $this->calendarInfo['{http://owncloud.org/ns}owner-principal']; |
|||
} |
|||
return parent::getOwner(); |
|||
} |
|||
|
|||
/** |
|||
* |
|||
*/ |
|||
public function delete() { |
|||
$this->caldavBackend->deleteSubscription($this->calendarInfo['id']); |
|||
} |
|||
|
|||
/** |
|||
* @param PropPatch $propPatch |
|||
*/ |
|||
public function propPatch(PropPatch $propPatch) { |
|||
$this->caldavBackend->updateSubscription($this->calendarInfo['id'], $propPatch); |
|||
} |
|||
|
|||
/** |
|||
* @param string $name |
|||
* @return CalendarObject|\Sabre\CalDAV\ICalendarObject |
|||
* @throws NotFound |
|||
*/ |
|||
public function getChild($name) { |
|||
$obj = $this->caldavBackend->getCalendarObject($this->calendarInfo['id'], $name, CalDavBackend::CALENDAR_TYPE_SUBSCRIPTION); |
|||
if (!$obj) { |
|||
throw new NotFound('Calendar object not found'); |
|||
} |
|||
|
|||
$obj['acl'] = $this->getChildACL(); |
|||
return new CachedSubscriptionObject ($this->caldavBackend, $this->calendarInfo, $obj); |
|||
|
|||
} |
|||
|
|||
/** |
|||
* @return array |
|||
*/ |
|||
public function getChildren():array { |
|||
$objs = $this->caldavBackend->getCalendarObjects($this->calendarInfo['id'], CalDavBackend::CALENDAR_TYPE_SUBSCRIPTION); |
|||
|
|||
$children = []; |
|||
foreach($objs as $obj) { |
|||
$children[] = new CachedSubscriptionObject($this->caldavBackend, $this->calendarInfo, $obj); |
|||
} |
|||
|
|||
return $children; |
|||
} |
|||
|
|||
/** |
|||
* @param array $paths |
|||
* @return array |
|||
*/ |
|||
public function getMultipleChildren(array $paths):array { |
|||
$objs = $this->caldavBackend->getMultipleCalendarObjects($this->calendarInfo['id'], $paths, CalDavBackend::CALENDAR_TYPE_SUBSCRIPTION); |
|||
|
|||
$children = []; |
|||
foreach($objs as $obj) { |
|||
$children[] = new CachedSubscriptionObject($this->caldavBackend, $this->calendarInfo, $obj); |
|||
} |
|||
|
|||
return $children; |
|||
} |
|||
|
|||
/** |
|||
* @param string $name |
|||
* @param null $calendarData |
|||
* @return null|string|void |
|||
* @throws MethodNotAllowed |
|||
*/ |
|||
public function createFile($name, $calendarData = null) { |
|||
throw new MethodNotAllowed('Creating objects in cached subscription is not allowed'); |
|||
} |
|||
|
|||
/** |
|||
* @param string $name |
|||
* @return bool |
|||
*/ |
|||
public function childExists($name):bool { |
|||
$obj = $this->caldavBackend->getCalendarObject($this->calendarInfo['id'], $name, CalDavBackend::CALENDAR_TYPE_SUBSCRIPTION); |
|||
if (!$obj) { |
|||
return false; |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
/** |
|||
* @param array $filters |
|||
* @return array |
|||
*/ |
|||
public function calendarQuery(array $filters):array { |
|||
return $this->caldavBackend->calendarQuery($this->calendarInfo['id'], $filters, CalDavBackend::CALENDAR_TYPE_SUBSCRIPTION); |
|||
} |
|||
} |
|||
@ -0,0 +1,64 @@ |
|||
<?php |
|||
declare(strict_types=1); |
|||
/** |
|||
* @copyright 2018 Georg Ehrke <oc.list@georgehrke.com> |
|||
* |
|||
* @author Georg Ehrke <oc.list@georgehrke.com> |
|||
* |
|||
* @license GNU AGPL version 3 or any later version |
|||
* |
|||
* This program is free software: you can redistribute it and/or modify |
|||
* it under the terms of the GNU Affero General Public License as |
|||
* published by the Free Software Foundation, either version 3 of the |
|||
* License, or (at your option) any later version. |
|||
* |
|||
* This program is distributed in the hope that it will be useful, |
|||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
* GNU Affero General Public License for more details. |
|||
* |
|||
* You should have received a copy of the GNU Affero General Public License |
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
|||
* |
|||
*/ |
|||
namespace OCA\DAV\CalDAV; |
|||
|
|||
use Sabre\DAV\Exception\MethodNotAllowed; |
|||
|
|||
/** |
|||
* Class CachedSubscriptionObject |
|||
* |
|||
* @package OCA\DAV\CalDAV |
|||
* @property CalDavBackend $caldavBackend |
|||
*/ |
|||
class CachedSubscriptionObject extends \Sabre\CalDAV\CalendarObject { |
|||
|
|||
/** |
|||
* @inheritdoc |
|||
*/ |
|||
public function get() { |
|||
// Pre-populating the 'calendardata' is optional, if we don't have it
|
|||
// already we fetch it from the backend.
|
|||
if (!isset($this->objectData['calendardata'])) { |
|||
$this->objectData = $this->caldavBackend->getCalendarObject($this->calendarInfo['id'], $this->objectData['uri'], CalDavBackend::CALENDAR_TYPE_SUBSCRIPTION); |
|||
} |
|||
|
|||
return $this->objectData['calendardata']; |
|||
} |
|||
|
|||
/** |
|||
* @param resource|string $calendarData |
|||
* @return string|void |
|||
* @throws MethodNotAllowed |
|||
*/ |
|||
public function put($calendarData) { |
|||
throw new MethodNotAllowed('Creating objects in a cached subscription is not allowed'); |
|||
} |
|||
|
|||
/** |
|||
* @throws MethodNotAllowed |
|||
*/ |
|||
public function delete() { |
|||
throw new MethodNotAllowed('Deleting objects in a cached subscription is not allowed'); |
|||
} |
|||
} |
|||
@ -0,0 +1,145 @@ |
|||
<?php |
|||
declare(strict_types=1); |
|||
/** |
|||
* @copyright 2018 Georg Ehrke <oc.list@georgehrke.com> |
|||
* |
|||
* @author Georg Ehrke <oc.list@georgehrke.com> |
|||
* |
|||
* @license GNU AGPL version 3 or any later version |
|||
* |
|||
* This program is free software: you can redistribute it and/or modify |
|||
* it under the terms of the GNU Affero General Public License as |
|||
* published by the Free Software Foundation, either version 3 of the |
|||
* License, or (at your option) any later version. |
|||
* |
|||
* This program is distributed in the hope that it will be useful, |
|||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
* GNU Affero General Public License for more details. |
|||
* |
|||
* You should have received a copy of the GNU Affero General Public License |
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
|||
* |
|||
*/ |
|||
namespace OCA\DAV\CalDAV\WebcalCaching; |
|||
|
|||
use OCA\DAV\CalDAV\CalendarHome; |
|||
use OCP\IRequest; |
|||
use Sabre\DAV\Exception\NotFound; |
|||
use Sabre\DAV\Server; |
|||
use Sabre\DAV\ServerPlugin; |
|||
use Sabre\HTTP\RequestInterface; |
|||
use Sabre\HTTP\ResponseInterface; |
|||
|
|||
class Plugin extends ServerPlugin { |
|||
|
|||
/** |
|||
* list of regular expressions for calendar user agents, |
|||
* that do not support subscriptions on their own |
|||
* |
|||
* @var string[] |
|||
*/ |
|||
const ENABLE_FOR_CLIENTS = []; |
|||
|
|||
/** |
|||
* @var bool |
|||
*/ |
|||
private $enabled=false; |
|||
|
|||
/** |
|||
* @var Server |
|||
*/ |
|||
private $server; |
|||
|
|||
/** |
|||
* Plugin constructor. |
|||
* |
|||
* @param IRequest $request |
|||
*/ |
|||
public function __construct(IRequest $request) { |
|||
if ($request->isUserAgent(self::ENABLE_FOR_CLIENTS)) { |
|||
$this->enabled = true; |
|||
} |
|||
|
|||
$magicHeader = $request->getHeader('X-NC-CalDAV-Webcal-Caching'); |
|||
if ($magicHeader === 'On') { |
|||
$this->enabled = true; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* This initializes the plugin. |
|||
* |
|||
* This function is called by Sabre\DAV\Server, after |
|||
* addPlugin is called. |
|||
* |
|||
* This method should set up the required event subscriptions. |
|||
* |
|||
* @param Server $server |
|||
*/ |
|||
public function initialize(Server $server) { |
|||
$this->server = $server; |
|||
$server->on('beforeMethod', [$this, 'beforeMethod']); |
|||
} |
|||
|
|||
/** |
|||
* @param RequestInterface $request |
|||
* @param ResponseInterface $response |
|||
*/ |
|||
public function beforeMethod(RequestInterface $request, ResponseInterface $response) { |
|||
if (!$this->enabled) { |
|||
return; |
|||
} |
|||
|
|||
$path = $request->getPath(); |
|||
$pathParts = explode('/', ltrim($path, '/')); |
|||
if (\count($pathParts) < 2) { |
|||
return; |
|||
} |
|||
|
|||
// $calendarHomePath will look like: calendars/username
|
|||
$calendarHomePath = $pathParts[0] . '/' . $pathParts[1]; |
|||
try { |
|||
$calendarHome = $this->server->tree->getNodeForPath($calendarHomePath); |
|||
if (!($calendarHome instanceof CalendarHome)) { |
|||
//how did we end up here?
|
|||
return; |
|||
} |
|||
|
|||
$calendarHome->enableCachedSubscriptionsForThisRequest(); |
|||
} catch(NotFound $ex) { |
|||
return; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* @return bool |
|||
*/ |
|||
public function isCachingEnabledForThisRequest():bool { |
|||
return $this->enabled; |
|||
} |
|||
|
|||
/** |
|||
* This method should return a list of server-features. |
|||
* |
|||
* This is for example 'versioning' and is added to the DAV: header |
|||
* in an OPTIONS response. |
|||
* |
|||
* @return string[] |
|||
*/ |
|||
public function getFeatures():array { |
|||
return ['nc-calendar-webcal-cache']; |
|||
} |
|||
|
|||
/** |
|||
* Returns a plugin name. |
|||
* |
|||
* Using this name other plugins will be able to access other plugins |
|||
* using Sabre\DAV\Server::getPlugin |
|||
* |
|||
* @return string |
|||
*/ |
|||
public function getPluginName():string { |
|||
return 'nc-calendar-webcal-cache'; |
|||
} |
|||
} |
|||
@ -0,0 +1,83 @@ |
|||
<?php |
|||
declare(strict_types=1); |
|||
/** |
|||
* @copyright 2018 Georg Ehrke <oc.list@georgehrke.com> |
|||
* |
|||
* @author Georg Ehrke <oc.list@georgehrke.com> |
|||
* |
|||
* @license GNU AGPL version 3 or any later version |
|||
* |
|||
* This program is free software: you can redistribute it and/or modify |
|||
* it under the terms of the GNU Affero General Public License as |
|||
* published by the Free Software Foundation, either version 3 of the |
|||
* License, or (at your option) any later version. |
|||
* |
|||
* This program is distributed in the hope that it will be useful, |
|||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
* GNU Affero General Public License for more details. |
|||
* |
|||
* You should have received a copy of the GNU Affero General Public License |
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
|||
* |
|||
*/ |
|||
|
|||
namespace OCA\DAV\Migration; |
|||
|
|||
use OCA\DAV\BackgroundJob\RefreshWebcalJob; |
|||
use OCP\BackgroundJob\IJobList; |
|||
use OCP\IDBConnection; |
|||
use OCP\Migration\IOutput; |
|||
use OCP\Migration\IRepairStep; |
|||
|
|||
class RefreshWebcalJobRegistrar implements IRepairStep { |
|||
|
|||
/** @var IDBConnection */ |
|||
private $connection; |
|||
|
|||
/** @var IJobList */ |
|||
private $jobList; |
|||
|
|||
/** |
|||
* FixBirthdayCalendarComponent constructor. |
|||
* |
|||
* @param IDBConnection $connection |
|||
* @param IJobList $jobList |
|||
*/ |
|||
public function __construct(IDBConnection $connection, IJobList $jobList) { |
|||
$this->connection = $connection; |
|||
$this->jobList = $jobList; |
|||
} |
|||
|
|||
/** |
|||
* @inheritdoc |
|||
*/ |
|||
public function getName() { |
|||
return 'Registering background jobs to update cache for webcal calendars'; |
|||
} |
|||
|
|||
/** |
|||
* @inheritdoc |
|||
*/ |
|||
public function run(IOutput $output) { |
|||
$query = $this->connection->getQueryBuilder(); |
|||
$query->select(['principaluri', 'uri']) |
|||
->from('calendarsubscriptions'); |
|||
$stmt = $query->execute(); |
|||
|
|||
$count = 0; |
|||
while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { |
|||
$args = [ |
|||
'principaluri' => $row['principaluri'], |
|||
'uri' => $row['uri'], |
|||
]; |
|||
|
|||
if (!$this->jobList->has(RefreshWebcalJob::class, $args)) { |
|||
$this->jobList->add(RefreshWebcalJob::class, $args); |
|||
$count++; |
|||
} |
|||
} |
|||
|
|||
$output->info("Added $count background jobs to update webcal calendars"); |
|||
} |
|||
} |
|||
@ -0,0 +1,105 @@ |
|||
<?php |
|||
declare(strict_types=1); |
|||
/** |
|||
* @copyright 2018 Georg Ehrke <oc.list@georgehrke.com> |
|||
* |
|||
* @author Georg Ehrke <oc.list@georgehrke.com> |
|||
* |
|||
* @license GNU AGPL version 3 or any later version |
|||
* |
|||
* This program is free software: you can redistribute it and/or modify |
|||
* it under the terms of the GNU Affero General Public License as |
|||
* published by the Free Software Foundation, either version 3 of the |
|||
* License, or (at your option) any later version. |
|||
* |
|||
* This program is distributed in the hope that it will be useful, |
|||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
* GNU Affero General Public License for more details. |
|||
* |
|||
* You should have received a copy of the GNU Affero General Public License |
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
|||
* |
|||
*/ |
|||
|
|||
namespace OCA\DAV\Migration; |
|||
|
|||
use Doctrine\DBAL\Types\Type; |
|||
use OCP\DB\ISchemaWrapper; |
|||
use OCP\Migration\SimpleMigrationStep; |
|||
use OCP\Migration\IOutput; |
|||
|
|||
class Version1006Date20180628111625 extends SimpleMigrationStep { |
|||
|
|||
/** |
|||
* @param IOutput $output |
|||
* @param \Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper` |
|||
* @param array $options |
|||
* @return null|ISchemaWrapper |
|||
*/ |
|||
public function changeSchema(IOutput $output, \Closure $schemaClosure, array $options) { |
|||
/** @var ISchemaWrapper $schema */ |
|||
$schema = $schemaClosure(); |
|||
|
|||
if ($schema->hasTable('calendarchanges')) { |
|||
$calendarChangesTable = $schema->getTable('calendarchanges'); |
|||
$calendarChangesTable->addColumn('calendartype', Type::INTEGER, [ |
|||
'notnull' => true, |
|||
'default' => 0, |
|||
]); |
|||
|
|||
if ($calendarChangesTable->hasIndex('calendarid_synctoken')) { |
|||
$calendarChangesTable->dropIndex('calendarid_synctoken'); |
|||
} |
|||
$calendarChangesTable->addIndex(['calendarid', 'calendartype', 'synctoken'], 'calendarid_calendartype_synctoken'); |
|||
} |
|||
|
|||
if ($schema->hasTable('calendarobjects')) { |
|||
$calendarObjectsTable = $schema->getTable('calendarobjects'); |
|||
$calendarObjectsTable->addColumn('calendartype', Type::INTEGER, [ |
|||
'notnull' => true, |
|||
'default' => 0, |
|||
]); |
|||
|
|||
if ($calendarObjectsTable->hasIndex('calobjects_index')) { |
|||
$calendarObjectsTable->dropIndex('calobjects_index'); |
|||
} |
|||
$calendarObjectsTable->addUniqueIndex(['calendarid', 'calendartype', 'uri'], 'calobjects_index'); |
|||
} |
|||
|
|||
if ($schema->hasTable('calendarobjects_props')) { |
|||
$calendarObjectsPropsTable = $schema->getTable('calendarobjects_props'); |
|||
$calendarObjectsPropsTable->addColumn('calendartype', Type::INTEGER, [ |
|||
'notnull' => true, |
|||
'default' => 0, |
|||
]); |
|||
|
|||
|
|||
if ($calendarObjectsPropsTable->hasIndex('calendarobject_index')) { |
|||
$calendarObjectsPropsTable->dropIndex('calendarobject_index'); |
|||
} |
|||
if ($calendarObjectsPropsTable->hasIndex('calendarobject_name_index')) { |
|||
$calendarObjectsPropsTable->dropIndex('calendarobject_name_index'); |
|||
} |
|||
if ($calendarObjectsPropsTable->hasIndex('calendarobject_value_index')) { |
|||
$calendarObjectsPropsTable->dropIndex('calendarobject_value_index'); |
|||
} |
|||
|
|||
$calendarObjectsPropsTable->addIndex(['objectid', 'calendartype'], 'calendarobject_index'); |
|||
$calendarObjectsPropsTable->addIndex(['name', 'calendartype'], 'calendarobject_name_index'); |
|||
$calendarObjectsPropsTable->addIndex(['value', 'calendartype'], 'calendarobject_value_index'); |
|||
} |
|||
|
|||
if ($schema->hasTable('calendarsubscriptions')) { |
|||
$calendarSubscriptionsTable = $schema->getTable('calendarsubscriptions'); |
|||
$calendarSubscriptionsTable->addColumn('synctoken', 'integer', [ |
|||
'notnull' => true, |
|||
'default' => 1, |
|||
'length' => 10, |
|||
'unsigned' => true, |
|||
]); |
|||
} |
|||
|
|||
return $schema; |
|||
} |
|||
} |
|||
@ -0,0 +1,242 @@ |
|||
<?php |
|||
/** |
|||
* @copyright Copyright (c) 2018, Georg Ehrke |
|||
* |
|||
* @author Georg Ehrke <oc.list@georgehrke.com> |
|||
* |
|||
* @license AGPL-3.0 |
|||
* |
|||
* This code is free software: you can redistribute it and/or modify |
|||
* it under the terms of the GNU Affero General Public License, version 3, |
|||
* as published by the Free Software Foundation. |
|||
* |
|||
* This program is distributed in the hope that it will be useful, |
|||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
* GNU Affero General Public License for more details. |
|||
* |
|||
* You should have received a copy of the GNU Affero General Public License, version 3, |
|||
* along with this program. If not, see <http://www.gnu.org/licenses/> |
|||
* |
|||
*/ |
|||
|
|||
namespace OCA\DAV\Tests\unit\BackgroundJob; |
|||
|
|||
use GuzzleHttp\HandlerStack; |
|||
use OCA\DAV\BackgroundJob\RefreshWebcalJob; |
|||
use OCA\DAV\CalDAV\CalDavBackend; |
|||
use OCP\AppFramework\Utility\ITimeFactory; |
|||
use OCP\BackgroundJob\IJobList; |
|||
use OCP\Http\Client\IClient; |
|||
use OCP\Http\Client\IClientService; |
|||
use OCP\Http\Client\IResponse; |
|||
use OCP\IConfig; |
|||
use OCP\ILogger; |
|||
use Test\TestCase; |
|||
|
|||
use Sabre\VObject; |
|||
|
|||
class RefreshWebcalJobTest extends TestCase { |
|||
|
|||
/** @var CalDavBackend | \PHPUnit_Framework_MockObject_MockObject */ |
|||
private $caldavBackend; |
|||
|
|||
/** @var IClientService | \PHPUnit_Framework_MockObject_MockObject */ |
|||
private $clientService; |
|||
|
|||
/** @var IConfig | \PHPUnit_Framework_MockObject_MockObject */ |
|||
private $config; |
|||
|
|||
/** @var ILogger | \PHPUnit_Framework_MockObject_MockObject */ |
|||
private $logger; |
|||
|
|||
/** @var ITimeFactory | \PHPUnit_Framework_MockObject_MockObject */ |
|||
private $timeFactory; |
|||
|
|||
/** @var IJobList | \PHPUnit_Framework_MockObject_MockObject */ |
|||
private $jobList; |
|||
|
|||
protected function setUp() { |
|||
parent::setUp(); |
|||
|
|||
$this->caldavBackend = $this->createMock(CalDavBackend::class); |
|||
$this->clientService = $this->createMock(IClientService::class); |
|||
$this->config = $this->createMock(IConfig::class); |
|||
$this->logger = $this->createMock(ILogger::class); |
|||
$this->timeFactory = $this->createMock(ITimeFactory::class); |
|||
|
|||
$this->jobList = $this->createMock(IJobList::class); |
|||
} |
|||
|
|||
/** |
|||
* @param string $body |
|||
* @param string $contentType |
|||
* @param string $result |
|||
* |
|||
* @dataProvider runDataProvider |
|||
*/ |
|||
public function testRun(string $body, string $contentType, string $result) { |
|||
$backgroundJob = new RefreshWebcalJob($this->caldavBackend, |
|||
$this->clientService, $this->config, $this->logger, $this->timeFactory); |
|||
|
|||
$backgroundJob->setArgument([ |
|||
'principaluri' => 'principals/users/testuser', |
|||
'uri' => 'sub123', |
|||
]); |
|||
$backgroundJob->setLastRun(0); |
|||
|
|||
$this->timeFactory->expects($this->once()) |
|||
->method('getTime') |
|||
->with() |
|||
->will($this->returnValue(1000000000)); |
|||
|
|||
$this->caldavBackend->expects($this->exactly(2)) |
|||
->method('getSubscriptionsForUser') |
|||
->with('principals/users/testuser') |
|||
->will($this->returnValue([ |
|||
[ |
|||
'id' => 99, |
|||
'uri' => 'sub456', |
|||
'refreshreate' => 'P1D', |
|||
'striptodos' => 1, |
|||
'stripalarms' => 1, |
|||
'stripattachments' => 1, |
|||
'source' => 'webcal://foo.bar/bla' |
|||
], |
|||
[ |
|||
'id' => 42, |
|||
'uri' => 'sub123', |
|||
'refreshreate' => 'P1H', |
|||
'striptodos' => 1, |
|||
'stripalarms' => 1, |
|||
'stripattachments' => 1, |
|||
'source' => 'webcal://foo.bar/bla2' |
|||
], |
|||
])); |
|||
|
|||
$client = $this->createMock(IClient::class); |
|||
$response = $this->createMock(IResponse::class); |
|||
$this->clientService->expects($this->once()) |
|||
->method('newClient') |
|||
->with() |
|||
->will($this->returnValue($client)); |
|||
|
|||
$this->config->expects($this->once()) |
|||
->method('getAppValue') |
|||
->with('dav', 'webcalAllowLocalAccess', 'no') |
|||
->will($this->returnValue('no')); |
|||
|
|||
$client->expects($this->once()) |
|||
->method('get') |
|||
->with('https://foo.bar/bla2', $this->callback(function($obj) { |
|||
return $obj['allow_redirects']['redirects'] === 10 && $obj['handler'] instanceof HandlerStack; |
|||
})) |
|||
->will($this->returnValue($response)); |
|||
|
|||
$response->expects($this->once()) |
|||
->method('getBody') |
|||
->with() |
|||
->will($this->returnValue($body)); |
|||
$response->expects($this->once()) |
|||
->method('getHeader') |
|||
->with('Content-Type') |
|||
->will($this->returnValue($contentType)); |
|||
|
|||
$this->caldavBackend->expects($this->once()) |
|||
->method('purgeAllCachedEventsForSubscription') |
|||
->with(42); |
|||
|
|||
$this->caldavBackend->expects($this->once()) |
|||
->method('createCalendarObject') |
|||
->with(42, '12345.ics', $result, 1); |
|||
|
|||
$backgroundJob->execute($this->jobList, $this->logger); |
|||
} |
|||
|
|||
/** |
|||
* @return array |
|||
*/ |
|||
public function runDataProvider():array { |
|||
return [ |
|||
[ |
|||
"BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nCALSCALE:GREGORIAN\r\nBEGIN:VEVENT\r\nUID:12345\r\nDTSTAMP:20160218T133704Z\r\nDTSTART;VALUE=DATE:19000101\r\nDTEND;VALUE=DATE:19000102\r\nRRULE:FREQ=YEARLY\r\nSUMMARY:12345's Birthday (1900)\r\nTRANSP:TRANSPARENT\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", |
|||
'text/calendar;charset=utf8', |
|||
"BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Sabre//Sabre VObject " . VObject\Version::VERSION . "//EN\r\nCALSCALE:GREGORIAN\r\nBEGIN:VEVENT\r\nUID:12345\r\nDTSTAMP:20160218T133704Z\r\nDTSTART;VALUE=DATE:19000101\r\nDTEND;VALUE=DATE:19000102\r\nRRULE:FREQ=YEARLY\r\nSUMMARY:12345's Birthday (1900)\r\nTRANSP:TRANSPARENT\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", |
|||
], |
|||
[ |
|||
'["vcalendar",[["prodid",{},"text","-//Example Corp.//Example Client//EN"],["version",{},"text","2.0"]],[["vtimezone",[["last-modified",{},"date-time","2004-01-10T03:28:45Z"],["tzid",{},"text","US/Eastern"]],[["daylight",[["dtstart",{},"date-time","2000-04-04T02:00:00"],["rrule",{},"recur",{"freq":"YEARLY","byday":"1SU","bymonth":4}],["tzname",{},"text","EDT"],["tzoffsetfrom",{},"utc-offset","-05:00"],["tzoffsetto",{},"utc-offset","-04:00"]],[]],["standard",[["dtstart",{},"date-time","2000-10-26T02:00:00"],["rrule",{},"recur",{"freq":"YEARLY","byday":"1SU","bymonth":10}],["tzname",{},"text","EST"],["tzoffsetfrom",{},"utc-offset","-04:00"],["tzoffsetto",{},"utc-offset","-05:00"]],[]]]],["vevent",[["dtstamp",{},"date-time","2006-02-06T00:11:21Z"],["dtstart",{"tzid":"US/Eastern"},"date-time","2006-01-02T14:00:00"],["duration",{},"duration","PT1H"],["recurrence-id",{"tzid":"US/Eastern"},"date-time","2006-01-04T12:00:00"],["summary",{},"text","Event #2"],["uid",{},"text","12345"]],[]]]]', |
|||
'application/calendar+json', |
|||
"BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Sabre//Sabre VObject " . VObject\Version::VERSION . "//EN\r\nCALSCALE:GREGORIAN\r\nBEGIN:VTIMEZONE\r\nLAST-MODIFIED:20040110T032845Z\r\nTZID:US/Eastern\r\nBEGIN:DAYLIGHT\r\nDTSTART:20000404T020000\r\nRRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4\r\nTZNAME:EDT\r\nTZOFFSETFROM:-0500\r\nTZOFFSETTO:-0400\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nDTSTART:20001026T020000\r\nRRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=10\r\nTZNAME:EST\r\nTZOFFSETFROM:-0400\r\nTZOFFSETTO:-0500\r\nEND:STANDARD\r\nEND:VTIMEZONE\r\nBEGIN:VEVENT\r\nDTSTAMP:20060206T001121Z\r\nDTSTART;TZID=US/Eastern:20060102T140000\r\nDURATION:PT1H\r\nRECURRENCE-ID;TZID=US/Eastern:20060104T120000\r\nSUMMARY:Event #2\r\nUID:12345\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n" |
|||
], |
|||
[ |
|||
'<?xml version="1.0" encoding="utf-8" ?><icalendar xmlns="urn:ietf:params:xml:ns:icalendar-2.0"><vcalendar><properties><prodid><text>-//Example Inc.//Example Client//EN</text></prodid><version><text>2.0</text></version></properties><components><vevent><properties><dtstamp><date-time>2006-02-06T00:11:21Z</date-time></dtstamp><dtstart><parameters><tzid><text>US/Eastern</text></tzid></parameters><date-time>2006-01-04T14:00:00</date-time></dtstart><duration><duration>PT1H</duration></duration><recurrence-id><parameters><tzid><text>US/Eastern</text></tzid></parameters><date-time>2006-01-04T12:00:00</date-time></recurrence-id><summary><text>Event #2 bis</text></summary><uid><text>12345</text></uid></properties></vevent></components></vcalendar></icalendar>', |
|||
'application/calendar+xml', |
|||
"BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Sabre//Sabre VObject " . VObject\Version::VERSION . "//EN\r\nCALSCALE:GREGORIAN\r\nBEGIN:VEVENT\r\nDTSTAMP:20060206T001121Z\r\nDTSTART;TZID=US/Eastern:20060104T140000\r\nDURATION:PT1H\r\nRECURRENCE-ID;TZID=US/Eastern:20060104T120000\r\nSUMMARY:Event #2 bis\r\nUID:12345\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n" |
|||
] |
|||
]; |
|||
} |
|||
|
|||
/** |
|||
* @dataProvider runLocalURLDataProvider |
|||
* |
|||
* @param string $source |
|||
*/ |
|||
public function testRunLocalURL($source) { |
|||
$backgroundJob = new RefreshWebcalJob($this->caldavBackend, |
|||
$this->clientService, $this->config, $this->logger, $this->timeFactory); |
|||
|
|||
$backgroundJob->setArgument([ |
|||
'principaluri' => 'principals/users/testuser', |
|||
'uri' => 'sub123', |
|||
]); |
|||
$backgroundJob->setLastRun(0); |
|||
|
|||
$this->timeFactory->expects($this->once()) |
|||
->method('getTime') |
|||
->with() |
|||
->will($this->returnValue(1000000000)); |
|||
|
|||
$this->caldavBackend->expects($this->exactly(2)) |
|||
->method('getSubscriptionsForUser') |
|||
->with('principals/users/testuser') |
|||
->will($this->returnValue([ |
|||
[ |
|||
'id' => 42, |
|||
'uri' => 'sub123', |
|||
'refreshreate' => 'P1H', |
|||
'striptodos' => 1, |
|||
'stripalarms' => 1, |
|||
'stripattachments' => 1, |
|||
'source' => $source |
|||
], |
|||
])); |
|||
|
|||
$client = $this->createMock(IClient::class); |
|||
$this->clientService->expects($this->once()) |
|||
->method('newClient') |
|||
->with() |
|||
->will($this->returnValue($client)); |
|||
|
|||
$this->config->expects($this->once()) |
|||
->method('getAppValue') |
|||
->with('dav', 'webcalAllowLocalAccess', 'no') |
|||
->will($this->returnValue('no')); |
|||
|
|||
$client->expects($this->never()) |
|||
->method('get'); |
|||
|
|||
$backgroundJob->execute($this->jobList, $this->logger); |
|||
} |
|||
|
|||
public function runLocalURLDataProvider():array { |
|||
return [ |
|||
['localhost/foo.bar'], |
|||
['[::1]/bla.blub'], |
|||
['192.168.0.1'], |
|||
['10.0.0.1'], |
|||
['another-host.local'], |
|||
['service.localhost'], |
|||
['!@#$'], // test invalid url
|
|||
]; |
|||
} |
|||
} |
|||
@ -0,0 +1,95 @@ |
|||
<?php |
|||
/** |
|||
* @copyright Copyright (c) 2018 Georg Ehrke |
|||
* |
|||
* @author Georg Ehrke <oc.list@georgehrke.com> |
|||
* |
|||
* @license AGPL-3.0 |
|||
* |
|||
* This code is free software: you can redistribute it and/or modify |
|||
* it under the terms of the GNU Affero General Public License, version 3, |
|||
* as published by the Free Software Foundation. |
|||
* |
|||
* This program is distributed in the hope that it will be useful, |
|||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
* GNU Affero General Public License for more details. |
|||
* |
|||
* You should have received a copy of the GNU Affero General Public License, version 3, |
|||
* along with this program. If not, see <http://www.gnu.org/licenses/> |
|||
* |
|||
*/ |
|||
|
|||
namespace OCA\DAV\Tests\unit\CalDAV; |
|||
|
|||
use OCA\DAV\CalDAV\CachedSubscriptionObject; |
|||
use OCA\DAV\CalDAV\CalDavBackend; |
|||
use OCA\DAV\CalDAV\Calendar; |
|||
use OCA\DAV\CalDAV\CalendarImpl; |
|||
|
|||
class CachedSubscriptionObjectTest extends \Test\TestCase { |
|||
|
|||
public function testGet() { |
|||
$backend = $this->createMock(CalDavBackend::class); |
|||
$calendarInfo = [ |
|||
'{http://owncloud.org/ns}owner-principal' => 'user1', |
|||
'principaluri' => 'user2', |
|||
'id' => 666, |
|||
'uri' => 'cal', |
|||
]; |
|||
$objectData = [ |
|||
'uri' => 'foo123' |
|||
]; |
|||
|
|||
$backend->expects($this->once()) |
|||
->method('getCalendarObject') |
|||
->with(666, 'foo123', 1) |
|||
->will($this->returnValue([ |
|||
'calendardata' => 'BEGIN...', |
|||
])); |
|||
|
|||
$calendarObject = new CachedSubscriptionObject($backend, $calendarInfo, $objectData); |
|||
$this->assertEquals('BEGIN...', $calendarObject->get()); |
|||
} |
|||
|
|||
/** |
|||
* @expectedException \Sabre\DAV\Exception\MethodNotAllowed |
|||
* @expectedExceptionMessage Creating objects in a cached subscription is not allowed |
|||
*/ |
|||
public function testPut() { |
|||
$backend = $this->createMock(CalDavBackend::class); |
|||
$calendarInfo = [ |
|||
'{http://owncloud.org/ns}owner-principal' => 'user1', |
|||
'principaluri' => 'user2', |
|||
'id' => 666, |
|||
'uri' => 'cal', |
|||
]; |
|||
$objectData = [ |
|||
'uri' => 'foo123' |
|||
]; |
|||
|
|||
$calendarObject = new CachedSubscriptionObject($backend, $calendarInfo, $objectData); |
|||
$calendarObject->put(''); |
|||
} |
|||
|
|||
/** |
|||
* @expectedException \Sabre\DAV\Exception\MethodNotAllowed |
|||
* @expectedExceptionMessage Deleting objects in a cached subscription is not allowed |
|||
*/ |
|||
public function testDelete() { |
|||
$backend = $this->createMock(CalDavBackend::class); |
|||
$calendarInfo = [ |
|||
'{http://owncloud.org/ns}owner-principal' => 'user1', |
|||
'principaluri' => 'user2', |
|||
'id' => 666, |
|||
'uri' => 'cal', |
|||
]; |
|||
$objectData = [ |
|||
'uri' => 'foo123' |
|||
]; |
|||
|
|||
$calendarObject = new CachedSubscriptionObject($backend, $calendarInfo, $objectData); |
|||
$calendarObject->delete(); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,300 @@ |
|||
<?php |
|||
/** |
|||
* @copyright Copyright (c) 2018 Georg Ehrke |
|||
* |
|||
* @author Georg Ehrke <oc.list@georgehrke.com> |
|||
* |
|||
* @license AGPL-3.0 |
|||
* |
|||
* This code is free software: you can redistribute it and/or modify |
|||
* it under the terms of the GNU Affero General Public License, version 3, |
|||
* as published by the Free Software Foundation. |
|||
* |
|||
* This program is distributed in the hope that it will be useful, |
|||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
* GNU Affero General Public License for more details. |
|||
* |
|||
* You should have received a copy of the GNU Affero General Public License, version 3, |
|||
* along with this program. If not, see <http://www.gnu.org/licenses/> |
|||
* |
|||
*/ |
|||
|
|||
namespace OCA\DAV\Tests\unit\CalDAV; |
|||
|
|||
use OCA\DAV\CalDAV\CachedSubscription; |
|||
use OCA\DAV\CalDAV\CachedSubscriptionObject; |
|||
use OCA\DAV\CalDAV\CalDavBackend; |
|||
use Sabre\DAV\PropPatch; |
|||
|
|||
class CachedSubscriptionTest extends \Test\TestCase { |
|||
|
|||
public function testGetACL() { |
|||
$backend = $this->createMock(CalDavBackend::class); |
|||
$calendarInfo = [ |
|||
'{http://owncloud.org/ns}owner-principal' => 'user1', |
|||
'principaluri' => 'user2', |
|||
'id' => 666, |
|||
'uri' => 'cal', |
|||
]; |
|||
|
|||
$calendar = new CachedSubscription($backend, $calendarInfo); |
|||
$this->assertEquals([ |
|||
[ |
|||
'privilege' => '{DAV:}read', |
|||
'principal' => 'user1', |
|||
'protected' => true, |
|||
], |
|||
[ |
|||
'privilege' => '{DAV:}read', |
|||
'principal' => 'user1/calendar-proxy-write', |
|||
'protected' => true, |
|||
], |
|||
[ |
|||
'privilege' => '{DAV:}read', |
|||
'principal' => 'user1/calendar-proxy-read', |
|||
'protected' => true, |
|||
], |
|||
[ |
|||
'privilege' => '{urn:ietf:params:xml:ns:caldav}read-free-busy', |
|||
'principal' => '{DAV:}authenticated', |
|||
'protected' => true, |
|||
], |
|||
], $calendar->getACL()); |
|||
} |
|||
|
|||
public function testGetChildACL() { |
|||
$backend = $this->createMock(CalDavBackend::class); |
|||
$calendarInfo = [ |
|||
'{http://owncloud.org/ns}owner-principal' => 'user1', |
|||
'principaluri' => 'user2', |
|||
'id' => 666, |
|||
'uri' => 'cal', |
|||
]; |
|||
|
|||
$calendar = new CachedSubscription($backend, $calendarInfo); |
|||
$this->assertEquals([ |
|||
[ |
|||
'privilege' => '{DAV:}read', |
|||
'principal' => 'user1', |
|||
'protected' => true, |
|||
], |
|||
[ |
|||
'privilege' => '{DAV:}read', |
|||
'principal' => 'user1/calendar-proxy-write', |
|||
'protected' => true, |
|||
], |
|||
[ |
|||
'privilege' => '{DAV:}read', |
|||
'principal' => 'user1/calendar-proxy-read', |
|||
'protected' => true, |
|||
] |
|||
], $calendar->getChildACL()); |
|||
} |
|||
|
|||
public function testGetOwner() { |
|||
$backend = $this->createMock(CalDavBackend::class); |
|||
$calendarInfo = [ |
|||
'{http://owncloud.org/ns}owner-principal' => 'user1', |
|||
'principaluri' => 'user2', |
|||
'id' => 666, |
|||
'uri' => 'cal', |
|||
]; |
|||
|
|||
$calendar = new CachedSubscription($backend, $calendarInfo); |
|||
$this->assertEquals('user1', $calendar->getOwner()); |
|||
} |
|||
|
|||
public function testDelete() { |
|||
$backend = $this->createMock(CalDavBackend::class); |
|||
$calendarInfo = [ |
|||
'{http://owncloud.org/ns}owner-principal' => 'user1', |
|||
'principaluri' => 'user2', |
|||
'id' => 666, |
|||
'uri' => 'cal', |
|||
]; |
|||
|
|||
$backend->expects($this->once()) |
|||
->method('deleteSubscription') |
|||
->with(666); |
|||
|
|||
$calendar = new CachedSubscription($backend, $calendarInfo); |
|||
$calendar->delete(); |
|||
} |
|||
|
|||
public function testPropPatch() { |
|||
$backend = $this->createMock(CalDavBackend::class); |
|||
$calendarInfo = [ |
|||
'{http://owncloud.org/ns}owner-principal' => 'user1', |
|||
'principaluri' => 'user2', |
|||
'id' => 666, |
|||
'uri' => 'cal', |
|||
]; |
|||
$propPatch = $this->createMock(PropPatch::class); |
|||
|
|||
$backend->expects($this->once()) |
|||
->method('updateSubscription') |
|||
->with(666, $propPatch); |
|||
|
|||
$calendar = new CachedSubscription($backend, $calendarInfo); |
|||
$calendar->propPatch($propPatch); |
|||
} |
|||
|
|||
/** |
|||
* @expectedException \Sabre\DAV\Exception\NotFound |
|||
* @expectedExceptionMessage Calendar object not found |
|||
*/ |
|||
public function testGetChild() { |
|||
$backend = $this->createMock(CalDavBackend::class); |
|||
$calendarInfo = [ |
|||
'{http://owncloud.org/ns}owner-principal' => 'user1', |
|||
'principaluri' => 'user2', |
|||
'id' => 666, |
|||
'uri' => 'cal', |
|||
]; |
|||
|
|||
$backend->expects($this->at(0)) |
|||
->method('getCalendarObject') |
|||
->with(666, 'foo1', 1) |
|||
->will($this->returnValue([ |
|||
'id' => 99, |
|||
'uri' => 'foo1' |
|||
])); |
|||
$backend->expects($this->at(1)) |
|||
->method('getCalendarObject') |
|||
->with(666, 'foo2', 1) |
|||
->will($this->returnValue(null)); |
|||
|
|||
$calendar = new CachedSubscription($backend, $calendarInfo); |
|||
|
|||
$first = $calendar->getChild('foo1'); |
|||
$this->assertInstanceOf(CachedSubscriptionObject::class, $first); |
|||
|
|||
$calendar->getChild('foo2'); |
|||
} |
|||
|
|||
public function testGetChildren() { |
|||
$backend = $this->createMock(CalDavBackend::class); |
|||
$calendarInfo = [ |
|||
'{http://owncloud.org/ns}owner-principal' => 'user1', |
|||
'principaluri' => 'user2', |
|||
'id' => 666, |
|||
'uri' => 'cal', |
|||
]; |
|||
|
|||
$backend->expects($this->at(0)) |
|||
->method('getCalendarObjects') |
|||
->with(666, 1) |
|||
->will($this->returnValue([ |
|||
[ |
|||
'id' => 99, |
|||
'uri' => 'foo1' |
|||
], |
|||
[ |
|||
'id' => 100, |
|||
'uri' => 'foo2' |
|||
], |
|||
])); |
|||
|
|||
$calendar = new CachedSubscription($backend, $calendarInfo); |
|||
|
|||
$res = $calendar->getChildren(); |
|||
$this->assertCount(2, $res); |
|||
$this->assertInstanceOf(CachedSubscriptionObject::class, $res[0]); |
|||
$this->assertInstanceOf(CachedSubscriptionObject::class, $res[1]); |
|||
} |
|||
|
|||
public function testGetMultipleChildren() { |
|||
$backend = $this->createMock(CalDavBackend::class); |
|||
$calendarInfo = [ |
|||
'{http://owncloud.org/ns}owner-principal' => 'user1', |
|||
'principaluri' => 'user2', |
|||
'id' => 666, |
|||
'uri' => 'cal', |
|||
]; |
|||
|
|||
$backend->expects($this->at(0)) |
|||
->method('getMultipleCalendarObjects') |
|||
->with(666, ['foo1', 'foo2'], 1) |
|||
->will($this->returnValue([ |
|||
[ |
|||
'id' => 99, |
|||
'uri' => 'foo1' |
|||
], |
|||
[ |
|||
'id' => 100, |
|||
'uri' => 'foo2' |
|||
], |
|||
])); |
|||
|
|||
$calendar = new CachedSubscription($backend, $calendarInfo); |
|||
|
|||
$res = $calendar->getMultipleChildren(['foo1', 'foo2']); |
|||
$this->assertCount(2, $res); |
|||
$this->assertInstanceOf(CachedSubscriptionObject::class, $res[0]); |
|||
$this->assertInstanceOf(CachedSubscriptionObject::class, $res[1]); |
|||
} |
|||
|
|||
/** |
|||
* @expectedException \Sabre\DAV\Exception\MethodNotAllowed |
|||
* @expectedExceptionMessage Creating objects in cached subscription is not allowed |
|||
*/ |
|||
public function testCreateFile() { |
|||
$backend = $this->createMock(CalDavBackend::class); |
|||
$calendarInfo = [ |
|||
'{http://owncloud.org/ns}owner-principal' => 'user1', |
|||
'principaluri' => 'user2', |
|||
'id' => 666, |
|||
'uri' => 'cal', |
|||
]; |
|||
|
|||
$calendar = new CachedSubscription($backend, $calendarInfo); |
|||
$calendar->createFile('foo', []); |
|||
} |
|||
|
|||
public function testChildExists() { |
|||
$backend = $this->createMock(CalDavBackend::class); |
|||
$calendarInfo = [ |
|||
'{http://owncloud.org/ns}owner-principal' => 'user1', |
|||
'principaluri' => 'user2', |
|||
'id' => 666, |
|||
'uri' => 'cal', |
|||
]; |
|||
|
|||
$backend->expects($this->at(0)) |
|||
->method('getCalendarObject') |
|||
->with(666, 'foo1', 1) |
|||
->will($this->returnValue([ |
|||
'id' => 99, |
|||
'uri' => 'foo1' |
|||
])); |
|||
$backend->expects($this->at(1)) |
|||
->method('getCalendarObject') |
|||
->with(666, 'foo2', 1) |
|||
->will($this->returnValue(null)); |
|||
|
|||
$calendar = new CachedSubscription($backend, $calendarInfo); |
|||
|
|||
$this->assertEquals(true, $calendar->childExists('foo1')); |
|||
$this->assertEquals(false, $calendar->childExists('foo2')); |
|||
} |
|||
|
|||
public function testCalendarQuery() { |
|||
$backend = $this->createMock(CalDavBackend::class); |
|||
$calendarInfo = [ |
|||
'{http://owncloud.org/ns}owner-principal' => 'user1', |
|||
'principaluri' => 'user2', |
|||
'id' => 666, |
|||
'uri' => 'cal', |
|||
]; |
|||
|
|||
$backend->expects($this->once()) |
|||
->method('calendarQuery') |
|||
->with(666, ['foo'], 1) |
|||
->will($this->returnValue([99])); |
|||
|
|||
$calendar = new CachedSubscription($backend, $calendarInfo); |
|||
|
|||
$this->assertEquals([99], $calendar->calendarQuery(['foo'])); |
|||
} |
|||
} |
|||
@ -0,0 +1,63 @@ |
|||
<?php |
|||
/** |
|||
* @copyright Copyright (c) 2018 Georg Ehrke |
|||
* |
|||
* @author Georg Ehrke <oc.list@georgehrke.com> |
|||
* |
|||
* @license AGPL-3.0 |
|||
* |
|||
* This code is free software: you can redistribute it and/or modify |
|||
* it under the terms of the GNU Affero General Public License, version 3, |
|||
* as published by the Free Software Foundation. |
|||
* |
|||
* This program is distributed in the hope that it will be useful, |
|||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
* GNU Affero General Public License for more details. |
|||
* |
|||
* You should have received a copy of the GNU Affero General Public License, version 3, |
|||
* along with this program. If not, see <http://www.gnu.org/licenses/> |
|||
* |
|||
*/ |
|||
|
|||
namespace OCA\DAV\Tests\unit\CalDAV\WebcalCaching; |
|||
|
|||
use OCA\DAV\CalDAV\WebcalCaching\Plugin; |
|||
use OCP\IRequest; |
|||
|
|||
class PluginTest extends \Test\TestCase { |
|||
|
|||
public function testDisabled() { |
|||
$request = $this->createMock(IRequest::class); |
|||
$request->expects($this->at(0)) |
|||
->method('isUserAgent') |
|||
->with([]) |
|||
->will($this->returnValue(false)); |
|||
|
|||
$request->expects($this->at(1)) |
|||
->method('getHeader') |
|||
->with('X-NC-CalDAV-Webcal-Caching') |
|||
->will($this->returnValue('')); |
|||
|
|||
$plugin = new Plugin($request); |
|||
|
|||
$this->assertEquals(false, $plugin->isCachingEnabledForThisRequest()); |
|||
} |
|||
|
|||
public function testEnabled() { |
|||
$request = $this->createMock(IRequest::class); |
|||
$request->expects($this->at(0)) |
|||
->method('isUserAgent') |
|||
->with([]) |
|||
->will($this->returnValue(false)); |
|||
|
|||
$request->expects($this->at(1)) |
|||
->method('getHeader') |
|||
->with('X-NC-CalDAV-Webcal-Caching') |
|||
->will($this->returnValue('On')); |
|||
|
|||
$plugin = new Plugin($request); |
|||
|
|||
$this->assertEquals(true, $plugin->isCachingEnabledForThisRequest()); |
|||
} |
|||
} |
|||
@ -0,0 +1,146 @@ |
|||
<?php |
|||
/** |
|||
* @copyright Copyright (c) 2018, Georg Ehrke |
|||
* |
|||
* @author Georg Ehrke <oc.list@georgehrke.com> |
|||
* |
|||
* @license AGPL-3.0 |
|||
* |
|||
* This code is free software: you can redistribute it and/or modify |
|||
* it under the terms of the GNU Affero General Public License, version 3, |
|||
* as published by the Free Software Foundation. |
|||
* |
|||
* This program is distributed in the hope that it will be useful, |
|||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
* GNU Affero General Public License for more details. |
|||
* |
|||
* You should have received a copy of the GNU Affero General Public License, version 3, |
|||
* along with this program. If not, see <http://www.gnu.org/licenses/> |
|||
* |
|||
*/ |
|||
|
|||
namespace OCA\DAV\Tests\unit\DAV\Migration; |
|||
|
|||
use OCA\DAV\BackgroundJob\RefreshWebcalJob; |
|||
use OCA\DAV\Migration\RefreshWebcalJobRegistrar; |
|||
use OCP\BackgroundJob\IJobList; |
|||
use OCP\DB\QueryBuilder\IQueryBuilder; |
|||
use OCP\IDBConnection; |
|||
use OCP\Migration\IOutput; |
|||
use Test\TestCase; |
|||
|
|||
class RefreshWebcalJobRegistrarTest extends TestCase { |
|||
|
|||
/** @var IDBConnection | \PHPUnit_Framework_MockObject_MockObject */ |
|||
private $db; |
|||
|
|||
/** @var IJobList | \PHPUnit_Framework_MockObject_MockObject */ |
|||
private $jobList; |
|||
|
|||
/** @var RefreshWebcalJobRegistrar */ |
|||
private $migration; |
|||
|
|||
protected function setUp() { |
|||
parent::setUp(); |
|||
|
|||
$this->db = $this->createMock(IDBConnection::class); |
|||
$this->jobList = $this->createMock(IJobList::class); |
|||
|
|||
$this->migration = new RefreshWebcalJobRegistrar($this->db, $this->jobList); |
|||
} |
|||
|
|||
public function testGetName() { |
|||
$this->assertEquals($this->migration->getName(), 'Registering background jobs to update cache for webcal calendars'); |
|||
} |
|||
|
|||
public function testRun() { |
|||
$output = $this->createMock(IOutput::class); |
|||
|
|||
$queryBuilder = $this->createMock(IQueryBuilder::class); |
|||
$statement = $this->createMock(\Doctrine\DBAL\Driver\Statement::class); |
|||
|
|||
$this->db->expects($this->once()) |
|||
->method('getQueryBuilder') |
|||
->will($this->returnValue($queryBuilder)); |
|||
|
|||
$queryBuilder->expects($this->at(0)) |
|||
->method('select') |
|||
->with(['principaluri', 'uri']) |
|||
->will($this->returnValue($queryBuilder)); |
|||
$queryBuilder->expects($this->at(1)) |
|||
->method('from') |
|||
->with('calendarsubscriptions') |
|||
->will($this->returnValue($queryBuilder)); |
|||
$queryBuilder->expects($this->at(2)) |
|||
->method('execute') |
|||
->will($this->returnValue($statement)); |
|||
|
|||
$statement->expects($this->at(0)) |
|||
->method('fetch') |
|||
->with(\PDO::FETCH_ASSOC) |
|||
->will($this->returnValue([ |
|||
'principaluri' => 'foo1', |
|||
'uri' => 'bar1', |
|||
])); |
|||
$statement->expects($this->at(1)) |
|||
->method('fetch') |
|||
->with(\PDO::FETCH_ASSOC) |
|||
->will($this->returnValue([ |
|||
'principaluri' => 'foo2', |
|||
'uri' => 'bar2', |
|||
])); |
|||
$statement->expects($this->at(2)) |
|||
->method('fetch') |
|||
->with(\PDO::FETCH_ASSOC) |
|||
->will($this->returnValue([ |
|||
'principaluri' => 'foo3', |
|||
'uri' => 'bar3', |
|||
])); |
|||
$statement->expects($this->at(0)) |
|||
->method('fetch') |
|||
->with(\PDO::FETCH_ASSOC) |
|||
->will($this->returnValue(null)); |
|||
|
|||
$this->jobList->expects($this->at(0)) |
|||
->method('has') |
|||
->with(RefreshWebcalJob::class, [ |
|||
'principaluri' => 'foo1', |
|||
'uri' => 'bar1', |
|||
]) |
|||
->will($this->returnValue(false)); |
|||
$this->jobList->expects($this->at(1)) |
|||
->method('add') |
|||
->with(RefreshWebcalJob::class, [ |
|||
'principaluri' => 'foo1', |
|||
'uri' => 'bar1', |
|||
]); |
|||
$this->jobList->expects($this->at(2)) |
|||
->method('has') |
|||
->with(RefreshWebcalJob::class, [ |
|||
'principaluri' => 'foo2', |
|||
'uri' => 'bar2', |
|||
]) |
|||
->will($this->returnValue(true)); |
|||
$this->jobList->expects($this->at(3)) |
|||
->method('has') |
|||
->with(RefreshWebcalJob::class, [ |
|||
'principaluri' => 'foo3', |
|||
'uri' => 'bar3', |
|||
]) |
|||
->will($this->returnValue(false)); |
|||
$this->jobList->expects($this->at(4)) |
|||
->method('add') |
|||
->with(RefreshWebcalJob::class, [ |
|||
'principaluri' => 'foo3', |
|||
'uri' => 'bar3', |
|||
]); |
|||
|
|||
$output->expects($this->once()) |
|||
->method('info') |
|||
->with('Added 2 background jobs to update webcal calendars'); |
|||
|
|||
$this->migration->run($output); |
|||
} |
|||
|
|||
} |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue