diff --git a/appinfo/database.xml b/appinfo/database.xml index d942c063..54153c46 100644 --- a/appinfo/database.xml +++ b/appinfo/database.xml @@ -345,6 +345,13 @@ true + + icon_id + text + 127 + true + + summary text @@ -374,5 +381,85 @@ + + *dbprefix*social_cache_documents + + + + id + string + 127 + true + true + + + + type + text + 31 + true + + + + media_type + text + 63 + true + + + + mime_type + text + 63 + true + + + + url + text + 127 + true + + + + local_copy + text + 127 + true + + + + public + boolean + true + + + + error + integer + 1 + true + + + + creation + timestamp + + + + caching + timestamp + + + + unique_url + true + + url + + + +
+ diff --git a/appinfo/info.xml b/appinfo/info.xml index 90e62061..1283707a 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -5,7 +5,7 @@ Social 🎉 Nextcloud becomes part of the federated social networks! - 0.0.37 + 0.0.43 agpl Maxence Lange Julius Härtl @@ -26,7 +26,12 @@ + + OCA\Social\Cron\Cache + + + OCA\Social\Command\CacheRefresh OCA\Social\Command\NoteCreate diff --git a/appinfo/routes.php b/appinfo/routes.php index 3703c956..6f86395e 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -20,8 +20,9 @@ return [ 'name' => 'Navigation#account', 'url' => '/account/{path}', 'verb' => 'GET', 'requirements' => ['path' => '.+'], 'defaults' => ['path' => ''] ], - // ['name' => 'Navigation#public', 'url' => '/{username}', 'verb' => 'GET'], - + ['name' => 'Navigation#public', 'url' => '/{username}', 'verb' => 'GET'], + ['name' => 'Navigation#documentGet', 'url' => '/document/get', 'verb' => 'GET'], + ['name' => 'Navigation#documentGetPublic', 'url' => '/document/public', 'verb' => 'GET'], // ['name' => 'Account#create', 'url' => '/local/account/{username}', 'verb' => 'POST'], ['name' => 'Account#info', 'url' => '/local/account/{username}', 'verb' => 'GET'], @@ -47,7 +48,9 @@ return [ ['name' => 'Local#accountsSearch', 'url' => '/api/v1/accounts/search', 'verb' => 'GET'], ['name' => 'Local#accountFollow', 'url' => '/api/v1/account/follow', 'verb' => 'PUT'], ['name' => 'Local#accountUnfollow', 'url' => '/api/v1/account/follow', 'verb' => 'DELETE'], + ['name' => 'Local#accountInfo', 'url' => '/api/v1/account/info', 'verb' => 'GET'], ['name' => 'Local#actorInfo', 'url' => '/api/v1/actor/info', 'verb' => 'GET'], + ['name' => 'Local#documentsCache', 'url' => '/api/v1/documents/cache', 'verb' => 'POST'], [ 'name' => 'Config#setCloudAddress', 'url' => '/api/v1/config/cloudAddress', diff --git a/lib/Command/CacheRefresh.php b/lib/Command/CacheRefresh.php new file mode 100644 index 00000000..fae76806 --- /dev/null +++ b/lib/Command/CacheRefresh.php @@ -0,0 +1,117 @@ + + * @copyright 2018, Maxence Lange + * @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 . + * + */ + + +namespace OCA\Social\Command; + + +use Exception; +use OC\Core\Command\Base; +use OCA\Social\Service\ActivityPub\DocumentService; +use OCA\Social\Service\ActivityPub\PersonService; +use OCA\Social\Service\ActorService; +use OCA\Social\Service\ConfigService; +use OCA\Social\Service\MiscService; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + + +class CacheRefresh extends Base { + + + /** @var ActorService */ + private $actorService; + + /** @var PersonService */ + private $personService; + + /** @var DocumentService */ + private $documentService; + + /** @var ConfigService */ + private $configService; + + /** @var MiscService */ + private $miscService; + + + /** + * CacheUpdate constructor. + * + * @param ActorService $actorService + * @param PersonService $personService + * @param DocumentService $documentService + * @param ConfigService $configService + * @param MiscService $miscService + */ + public function __construct( + ActorService $actorService, PersonService $personService, DocumentService $documentService, + ConfigService $configService, MiscService $miscService + ) { + parent::__construct(); + + $this->actorService = $actorService; + $this->personService = $personService; + $this->documentService = $documentService; + $this->configService = $configService; + $this->miscService = $miscService; + } + + + /** + * + */ + protected function configure() { + parent::configure(); + $this->setName('social:cache:refresh') + ->setDescription('Update the cache'); + } + + + /** + * @param InputInterface $input + * @param OutputInterface $output + * + * @throws Exception + */ + protected function execute(InputInterface $input, OutputInterface $output) { + + $result = $this->actorService->manageCacheLocalActors(); + $output->writeLn($result . ' local accounts regenerated'); + + $result = $this->personService->manageCacheRemoteActors(); + $output->writeLn($result . ' remote accounts updated'); + + $result = $this->documentService->manageCacheDocuments(); + $output->writeLn($result . ' documents cached'); + } + + +} + diff --git a/lib/Controller/LocalController.php b/lib/Controller/LocalController.php index c824aeaa..1217d523 100644 --- a/lib/Controller/LocalController.php +++ b/lib/Controller/LocalController.php @@ -37,6 +37,7 @@ use OCA\Social\AppInfo\Application; use OCA\Social\Exceptions\InvalidResourceException; use OCA\Social\Model\ActivityPub\ACore; use OCA\Social\Model\Post; +use OCA\Social\Service\ActivityPub\DocumentService; use OCA\Social\Service\ActivityPub\FollowService; use OCA\Social\Service\ActivityPub\NoteService; use OCA\Social\Service\ActivityPub\PersonService; @@ -78,6 +79,9 @@ class LocalController extends Controller { /** @var NoteService */ private $noteService; + /** @var DocumentService */ + private $documentService; + /** @var MiscService */ private $miscService; @@ -92,12 +96,14 @@ class LocalController extends Controller { * @param ActorService $actorService * @param PostService $postService * @param NoteService $noteService + * @param DocumentService $documentService * @param MiscService $miscService */ public function __construct( IRequest $request, $userId, PersonService $personService, FollowService $followService, ActorService $actorService, PostService $postService, NoteService $noteService, + DocumentService $documentService, MiscService $miscService ) { parent::__construct(Application::APP_NAME, $request); @@ -109,6 +115,7 @@ class LocalController extends Controller { $this->followService = $followService; $this->postService = $postService; $this->noteService = $noteService; + $this->documentService = $documentService; $this->miscService = $miscService; } @@ -179,6 +186,9 @@ class LocalController extends Controller { * @NoAdminRequired * @NoSubAdminRequired * + * @param int $since + * @param int $limit + * * @return DataResponse */ public function streamHome(int $since = 0, int $limit = 5): DataResponse { @@ -343,6 +353,29 @@ class LocalController extends Controller { } + /** + * + * // TODO: Delete the NoCSRF check + * + * @NoCSRFRequired + * @NoAdminRequired + * @NoSubAdminRequired + * + * @param string $account + * + * @return DataResponse + */ + public function accountInfo(string $account): DataResponse { + try { + $actor = $this->personService->getFromAccount($account); + + return $this->success(['account' => $actor]); + } catch (Exception $e) { + return $this->fail($e); + } + } + + /** * * // TODO: Delete the NoCSRF check @@ -365,4 +398,35 @@ class LocalController extends Controller { } } + + /** + * // TODO: Delete the NoCSRF check + * + * @NoCSRFRequired + * @NoAdminRequired + * @NoSubAdminRequired + * + * @param array $documents + * + * @return DataResponse + */ + public function documentsCache(array $documents): DataResponse { + try { + $cached = []; + foreach ($documents as $id) { + try { + + $document = $this->documentService->cacheRemoteDocument($id); + $cached[] = $document; + } catch (Exception $e) { + } + } + + return $this->success($cached); + } catch (Exception $e) { + return $this->fail($e); + } + } + } + diff --git a/lib/Controller/NavigationController.php b/lib/Controller/NavigationController.php index 54c7e184..864b9eb8 100644 --- a/lib/Controller/NavigationController.php +++ b/lib/Controller/NavigationController.php @@ -32,16 +32,22 @@ namespace OCA\Social\Controller; use daita\MySmallPhpTools\Traits\TArrayTools; use daita\MySmallPhpTools\Traits\Nextcloud\TNCDataResponse; +use Exception; +use OC\Files\Node\File; +use OC\Files\SimpleFS\SimpleFile; use OC\User\NoUserException; use OCA\Social\AppInfo\Application; use OCA\Social\Exceptions\AccountAlreadyExistsException; use OCA\Social\Exceptions\SocialAppConfigException; +use OCA\Social\Service\ActivityPub\DocumentService; use OCA\Social\Service\ActorService; use OCA\Social\Service\ConfigService; use OCA\Social\Service\MiscService; use OCP\AppFramework\Controller; use OCP\AppFramework\Http\DataResponse; +use OCP\AppFramework\Http\FileDisplayResponse; use OCP\AppFramework\Http\RedirectResponse; +use OCP\AppFramework\Http\Response; use OCP\AppFramework\Http\Template\PublicTemplateResponse; use OCP\AppFramework\Http\TemplateResponse; use OCP\IConfig; @@ -68,6 +74,8 @@ class NavigationController extends Controller { /** @var ActorService */ private $actorService; + private $documentService; + /** @var ConfigService */ private $configService; @@ -85,14 +93,15 @@ class NavigationController extends Controller { * @param IConfig $config * @param IURLGenerator $urlGenerator * @param ActorService $actorService + * @param DocumentService $documentService * @param ConfigService $configService * @param MiscService $miscService * @param IL10N $l10n */ public function __construct( IRequest $request, $userId, IConfig $config, IURLGenerator $urlGenerator, - ActorService $actorService, ConfigService $configService, MiscService $miscService, - IL10N $l10n + ActorService $actorService, DocumentService $documentService, ConfigService $configService, + MiscService $miscService, IL10N $l10n ) { parent::__construct(Application::APP_NAME, $request); @@ -101,6 +110,7 @@ class NavigationController extends Controller { $this->urlGenerator = $urlGenerator; $this->actorService = $actorService; + $this->documentService = $documentService; $this->configService = $configService; $this->miscService = $miscService; $this->l10n = $l10n; @@ -130,13 +140,18 @@ class NavigationController extends Controller { $data['serverData']['cloudAddress'] = $this->configService->getCloudAddress(); } catch (SocialAppConfigException $e) { $data['serverData']['setup'] = true; - $data['serverData']['isAdmin'] = \OC::$server->getGroupManager()->isAdmin($this->userId); + $data['serverData']['isAdmin'] = \OC::$server->getGroupManager() + ->isAdmin($this->userId); if ($data['serverData']['isAdmin']) { $cloudAddress = $this->request->getParam('cloudAddress'); if ($cloudAddress !== null) { $this->configService->setCloudAddress($cloudAddress); } else { - $data['serverData']['cliUrl'] = $this->config->getSystemValue('overwrite.cli.url', \OC::$server->getURLGenerator()->getBaseUrl()); + $data['serverData']['cliUrl'] = $this->config->getSystemValue( + 'overwrite.cli.url', \OC::$server->getURLGenerator() + ->getBaseUrl() + ); + return new TemplateResponse(Application::APP_NAME, 'main', $data); } } @@ -153,8 +168,6 @@ class NavigationController extends Controller { } - - /** * Display the navigation page of the Social app. * @@ -237,4 +250,54 @@ class NavigationController extends Controller { return $page; } + + /** + * + * // TODO: Delete the NoCSRF check + * + * @NoCSRFRequired + * @NoAdminRequired + * @NoSubAdminRequired + * + * @param string $id + * + * @return Response + */ + public function documentGet(string $id): Response { + + try { + $file = $this->documentService->getFromCache($id); + + return new FileDisplayResponse($file); + } catch (Exception $e) { + return $this->fail($e); + } + } + + + /** + * + * // TODO: Delete the NoCSRF check + * + * @PublicPage + * @NoCSRFRequired + * @NoAdminRequired + * @NoSubAdminRequired + * + * @param string $id + * + * @return Response + */ + public function documentGetPublic(string $id): Response { + + try { + $file = $this->documentService->getFromCache($id, true); + + return new FileDisplayResponse($file); + } catch (Exception $e) { + return $this->fail($e); + } + } + } + diff --git a/lib/Cron/Cache.php b/lib/Cron/Cache.php new file mode 100644 index 00000000..df9b741a --- /dev/null +++ b/lib/Cron/Cache.php @@ -0,0 +1,114 @@ + + * @copyright 2018, Maxence Lange + * @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 . + * + */ + + +namespace OCA\Social\Cron; + + +use Exception; +use OC\BackgroundJob\TimedJob; +use OCA\Social\AppInfo\Application; +use OCA\Social\Service\ActivityPub\DocumentService; +use OCA\Social\Service\ActivityPub\PersonService; +use OCA\Social\Service\ActorService; +use OCA\Social\Service\ConfigService; +use OCA\Social\Service\MiscService; +use OCP\AppFramework\QueryException; + + +/** + * Class Cache + * + * @package OCA\Social\Cron + */ +class Cache extends TimedJob { + + + /** @var ActorService */ + private $actorService; + + /** @var PersonService */ + private $personService; + + /** @var DocumentService */ + private $documentService; + + /** @var ConfigService */ + private $configService; + + /** @var MiscService */ + private $miscService; + + + /** + * Cache constructor. + */ + public function __construct() { + $this->setInterval(12 * 60); // 12 minutes + } + + + /** + * @param mixed $argument + * + * @throws QueryException + */ + protected function run($argument) { + $app = new Application(); + $c = $app->getContainer(); + + $this->actorService = $c->query(ActorService::class); + $this->personService = $c->query(PersonService::class); + $this->documentService = $c->query(DocumentService::class); + $this->configService = $c->query(ConfigService::class); + $this->miscService = $c->query(MiscService::class); + + $this->manageCache(); + } + + + private function manageCache() { + try { + $this->actorService->manageCacheLocalActors(); + } catch (Exception $e) { + } + + try { + $this->personService->manageCacheRemoteActors(); + } catch (Exception $e) { + } + + try { + $this->documentService->manageCacheDocuments(); + } catch (Exception $e) { + } + } + + +} diff --git a/lib/Db/ActorsRequest.php b/lib/Db/ActorsRequest.php index 208f9723..09b37e4b 100644 --- a/lib/Db/ActorsRequest.php +++ b/lib/Db/ActorsRequest.php @@ -64,7 +64,7 @@ class ActorsRequest extends ActorsRequestBuilder { */ public function create(Person $actor): string { - $id = $this->configService->getUrlRoot() . '@' . $actor->getPreferredUsername(); + $id = $this->configService->getUrlSocial() . '@' . $actor->getPreferredUsername(); try { $qb = $this->getActorsInsertSql(); @@ -161,6 +161,24 @@ class ActorsRequest extends ActorsRequestBuilder { } + /** + * @return Person[] + * @throws SocialAppConfigException + */ + public function getAll(): array { + $qb = $this->getActorsSelectSql(); + + $accounts = []; + $cursor = $qb->execute(); + while ($data = $cursor->fetch()) { + $accounts[] = $this->parseActorsSelectSql($data); + } + $cursor->closeCursor(); + + return $accounts; + } + + /** * @param string $search * diff --git a/lib/Db/ActorsRequestBuilder.php b/lib/Db/ActorsRequestBuilder.php index 3d11b9ca..5cf283cb 100644 --- a/lib/Db/ActorsRequestBuilder.php +++ b/lib/Db/ActorsRequestBuilder.php @@ -109,10 +109,10 @@ class ActorsRequestBuilder extends CoreRequestBuilder { * @throws SocialAppConfigException */ protected function parseActorsSelectSql($data): Person { - $root = $this->configService->getUrlRoot(); + $root = $this->configService->getUrlSocial(); $actor = new Person(); - $actor->import($data); + $actor->importFromDatabase($data); $actor->setInbox($actor->getId() . '/inbox') ->setOutbox($actor->getId() . '/outbox') ->setFollowers($actor->getId() . '/followers') @@ -122,7 +122,7 @@ class ActorsRequestBuilder extends CoreRequestBuilder { ->setAccount( $actor->getPreferredUsername() . '@' . $this->configService->getCloudAddress(true) ); - $actor->setUrlRoot($root) + $actor->setUrlSocial($root) ->setUrl($actor->getId()); return $actor; diff --git a/lib/Db/CacheActorsRequest.php b/lib/Db/CacheActorsRequest.php index fe722dcc..dd3d9489 100644 --- a/lib/Db/CacheActorsRequest.php +++ b/lib/Db/CacheActorsRequest.php @@ -30,15 +30,20 @@ declare(strict_types=1); namespace OCA\Social\Db; +use DateTime; +use Exception; use OCA\Social\Exceptions\CacheActorDoesNotExistException; use OCA\Social\Model\ActivityPub\Person; use OCA\Social\Service\ConfigService; use OCA\Social\Service\MiscService; +use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\IDBConnection; class CacheActorsRequest extends CacheActorsRequestBuilder { + const CACHE_TTL = 60 * 24; // 1d + /** * CacheActorsRequest constructor. * @@ -78,7 +83,17 @@ class CacheActorsRequest extends CacheActorsRequestBuilder { ->setValue('name', $qb->createNamedParameter($actor->getName())) ->setValue('summary', $qb->createNamedParameter($actor->getSummary())) ->setValue('public_key', $qb->createNamedParameter($actor->getPublicKey())) - ->setValue('source', $qb->createNamedParameter($actor->getSource())); + ->setValue('source', $qb->createNamedParameter($actor->getSource())) + ->setValue( + 'creation', + $qb->createNamedParameter(new DateTime('now'), IQueryBuilder::PARAM_DATE) + ); + + if ($actor->gotIcon()) { + $iconId = $actor->getIcon() + ->getId(); + $qb->setValue('icon_id', $qb->createNamedParameter($iconId)); + } $qb->execute(); @@ -86,30 +101,6 @@ class CacheActorsRequest extends CacheActorsRequestBuilder { } -// /** -// * get Cached value about an Actor, based on the account. -// * -// * @param string $account -// * -// * @return CacheActor -// * @throws CacheActorDoesNotExistException -// */ -// public function getFromAccount(string $account): CacheActor { -// $qb = $this->getCacheActorsSelectSql(); -// $this->limitToAccount($qb, $account); -// -// $cursor = $qb->execute(); -// $data = $cursor->fetch(); -// $cursor->closeCursor(); -// -// if ($data === false) { -// throw new CacheActorDoesNotExistException(); -// } -// -// return $this->parseCacheActorsSelectSql($data); -// } - - /** * get Cached version of an Actor, based on the UriId * @@ -121,6 +112,7 @@ class CacheActorsRequest extends CacheActorsRequestBuilder { public function getFromId(string $id): Person { $qb = $this->getCacheActorsSelectSql(); $this->limitToIdString($qb, $id); + $this->leftJoinCacheDocuments($qb, 'icon_id'); $cursor = $qb->execute(); $data = $cursor->fetch(); @@ -145,6 +137,7 @@ class CacheActorsRequest extends CacheActorsRequestBuilder { public function getFromAccount(string $account): Person { $qb = $this->getCacheActorsSelectSql(); $this->limitToAccount($qb, $account); + $this->leftJoinCacheDocuments($qb, 'icon_id'); $cursor = $qb->execute(); $data = $cursor->fetch(); @@ -166,6 +159,7 @@ class CacheActorsRequest extends CacheActorsRequestBuilder { public function searchAccounts(string $search): array { $qb = $this->getCacheActorsSelectSql(); $this->searchInAccount($qb, $search); + $this->leftJoinCacheDocuments($qb, 'icon_id'); $accounts = []; $cursor = $qb->execute(); @@ -178,6 +172,26 @@ class CacheActorsRequest extends CacheActorsRequestBuilder { } + /** + * @return Person[] + * @throws Exception + */ + public function getRemoteActorsToUpdate(): array { + $qb = $this->getCacheActorsSelectSql(); + $this->limitToLocal($qb, false); + $this->limitToCreation($qb, self::CACHE_TTL); + + $update = []; + $cursor = $qb->execute(); + while ($data = $cursor->fetch()) { + $update[] = $this->parseCacheActorsSelectSql($data); + } + $cursor->closeCursor(); + + return $update; + } + + /** * delete cached version of an Actor, based on the UriId * diff --git a/lib/Db/CacheActorsRequestBuilder.php b/lib/Db/CacheActorsRequestBuilder.php index 7f91e976..3d6847d1 100644 --- a/lib/Db/CacheActorsRequestBuilder.php +++ b/lib/Db/CacheActorsRequestBuilder.php @@ -31,7 +31,7 @@ namespace OCA\Social\Db; use daita\MySmallPhpTools\Traits\TArrayTools; -use OCA\Social\Model\ActivityPub\Cache\CacheActor; +use OCA\Social\Exceptions\InvalidResourceException; use OCA\Social\Model\ActivityPub\Person; use OCP\DB\QueryBuilder\IQueryBuilder; @@ -110,7 +110,14 @@ class CacheActorsRequestBuilder extends CoreRequestBuilder { */ protected function parseCacheActorsSelectSql(array $data): Person { $actor = new Person(); - $actor->import($data); + $actor->importFromDatabase($data); + + try { + $icon = $this->parseCacheDocumentsLeftJoin($data); + $icon->setParent($actor); + $actor->setIcon($icon); + } catch (InvalidResourceException $e) { + } return $actor; } diff --git a/lib/Db/CacheDocumentsRequest.php b/lib/Db/CacheDocumentsRequest.php new file mode 100644 index 00000000..6c7f7b68 --- /dev/null +++ b/lib/Db/CacheDocumentsRequest.php @@ -0,0 +1,165 @@ + + * @copyright 2018, Maxence Lange + * @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 . + * + */ + +namespace OCA\Social\Db; + + +use DateTime; +use Exception; +use OCA\Social\Exceptions\CacheDocumentDoesNotExistException; +use OCA\Social\Model\ActivityPub\Document; +use OCP\DB\QueryBuilder\IQueryBuilder; + +class CacheDocumentsRequest extends CacheDocumentsRequestBuilder { + + + const CACHE_TTL = 15; // 15 min + + + /** + * insert cache about an Actor in database. + * + * @param Document $document + */ + public function save(Document $document) { + $qb = $this->getCacheDocumentsInsertSql(); + $qb->setValue('id', $qb->createNamedParameter($document->getId())) + ->setValue('type', $qb->createNamedParameter($document->getType())) + ->setValue('url', $qb->createNamedParameter($document->getUrl())) + ->setValue('media_type', $qb->createNamedParameter($document->getMediaType())) + ->setValue('mime_type', $qb->createNamedParameter($document->getMimeType())) + ->setValue('error', $qb->createNamedParameter($document->getError())) + ->setValue('local_copy', $qb->createNamedParameter($document->getLocalCopy())) + ->setValue('public', $qb->createNamedParameter(($document->isPublic()) ? '1' : '0')) + ->setValue( + 'creation', + $qb->createNamedParameter(new DateTime('now'), IQueryBuilder::PARAM_DATE) + ); + $qb->execute(); + } + + + /** + * @param Document $document + */ + public function initCaching(Document $document) { + $qb = $this->getCacheDocumentsUpdateSql(); + $this->limitToIdString($qb, $document->getId()); + $qb->set( + 'caching', $qb->createNamedParameter(new DateTime('now'), IQueryBuilder::PARAM_DATE) + ); + + $qb->execute(); + } + + + /** + * @param Document $document + */ + public function endCaching(Document $document) { + $qb = $this->getCacheDocumentsUpdateSql(); + $this->limitToIdString($qb, $document->getId()); + $qb->set('local_copy', $qb->createNamedParameter($document->getLocalCopy())); + $qb->set('error', $qb->createNamedParameter($document->getError())); + + $qb->execute(); + } + + + /** + * @param string $url + * + * @return Document + * @throws CacheDocumentDoesNotExistException + */ + public function getBySource(string $url) { + $qb = $this->getCacheDocumentsSelectSql(); + $this->limitToUrl($qb, $url); + + $cursor = $qb->execute(); + $data = $cursor->fetch(); + $cursor->closeCursor(); + + if ($data === false) { + throw new CacheDocumentDoesNotExistException(); + } + + return $this->parseCacheDocumentsSelectSql($data); + } + + + /** + * @param string $id + * + * @param bool $public + * + * @return Document + * @throws CacheDocumentDoesNotExistException + */ + public function getById(string $id, bool $public = false) { + $qb = $this->getCacheDocumentsSelectSql(); + $this->limitToIdString($qb, $id); + + if ($public === true) { + $this->limitToPublic($qb); + } + + $cursor = $qb->execute(); + $data = $cursor->fetch(); + $cursor->closeCursor(); + + if ($data === false) { + throw new CacheDocumentDoesNotExistException(); + } + + return $this->parseCacheDocumentsSelectSql($data); + } + + + /** + * @return Document[] + * @throws Exception + */ + public function getNotCachedDocuments() { + $qb = $this->getCacheDocumentsSelectSql(); + $this->limitToDBFieldEmpty($qb, 'local_copy'); + $this->limitToCaching($qb, self::CACHE_TTL); + + $documents = []; + $cursor = $qb->execute(); + while ($data = $cursor->fetch()) { + $documents[] = $this->parseCacheDocumentsSelectSql($data); + } + $cursor->closeCursor(); + + return $documents; + } + +} + diff --git a/lib/Db/CacheDocumentsRequestBuilder.php b/lib/Db/CacheDocumentsRequestBuilder.php new file mode 100644 index 00000000..56e0c223 --- /dev/null +++ b/lib/Db/CacheDocumentsRequestBuilder.php @@ -0,0 +1,116 @@ + + * @copyright 2018, Maxence Lange + * @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 . + * + */ + +namespace OCA\Social\Db; + + +use daita\MySmallPhpTools\Traits\TArrayTools; +use OCA\Social\Model\ActivityPub\Document; +use OCP\DB\QueryBuilder\IQueryBuilder; + +class CacheDocumentsRequestBuilder extends CoreRequestBuilder { + + + use TArrayTools; + + + /** + * Base of the Sql Insert request + * + * @return IQueryBuilder + */ + protected function getCacheDocumentsInsertSql(): IQueryBuilder { + $qb = $this->dbConnection->getQueryBuilder(); + $qb->insert(self::TABLE_CACHE_DOCUMENTS); + + return $qb; + } + + + /** + * Base of the Sql Update request + * + * @return IQueryBuilder + */ + protected function getCacheDocumentsUpdateSql(): IQueryBuilder { + $qb = $this->dbConnection->getQueryBuilder(); + $qb->update(self::TABLE_CACHE_DOCUMENTS); + + return $qb; + } + + + /** + * Base of the Sql Select request for Shares + * + * @return IQueryBuilder + */ + protected function getCacheDocumentsSelectSql(): IQueryBuilder { + $qb = $this->dbConnection->getQueryBuilder(); + + /** @noinspection PhpMethodParametersCountMismatchInspection */ + $qb->select( + 'cd.id', 'cd.type', 'cd.media_type', 'cd.mime_type', 'cd.url', 'cd.local_copy', + 'cd.public', 'cd.error', 'cd.creation', 'cd.caching' + ) + ->from(self::TABLE_CACHE_DOCUMENTS, 'cd'); + + $this->defaultSelectAlias = 'cd'; + + return $qb; + } + + + /** + * Base of the Sql Delete request + * + * @return IQueryBuilder + */ + protected function getCacheDocumentsDeleteSql(): IQueryBuilder { + $qb = $this->dbConnection->getQueryBuilder(); + $qb->delete(self::TABLE_CACHE_DOCUMENTS); + + return $qb; + } + + + /** + * @param array $data + * + * @return Document + */ + protected function parseCacheDocumentsSelectSql(array $data): Document { + $document = new Document(); + $document->importFromDatabase($data); + + return $document; + } + +} + diff --git a/lib/Db/CoreRequestBuilder.php b/lib/Db/CoreRequestBuilder.php index 7c41e594..538d9751 100644 --- a/lib/Db/CoreRequestBuilder.php +++ b/lib/Db/CoreRequestBuilder.php @@ -27,24 +27,38 @@ declare(strict_types=1); * */ + namespace OCA\Social\Db; +use DateInterval; +use DateTime; use Doctrine\DBAL\Query\QueryBuilder; +use Exception; use OCA\Social\Exceptions\InvalidResourceException; +use OCA\Social\Model\ActivityPub\Document; +use OCA\Social\Model\ActivityPub\Image; use OCA\Social\Model\ActivityPub\Person; use OCA\Social\Service\ConfigService; use OCA\Social\Service\MiscService; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\IDBConnection; + +/** + * Class CoreRequestBuilder + * + * @package OCA\Social\Db + */ class CoreRequestBuilder { + const TABLE_SERVER_ACTORS = 'social_server_actors'; const TABLE_SERVER_NOTES = 'social_server_notes'; const TABLE_SERVER_FOLLOWS = 'social_server_follows'; const TABLE_CACHE_ACTORS = 'social_cache_actors'; + const TABLE_CACHE_DOCUMENTS = 'social_cache_documents'; /** @var IDBConnection */ @@ -134,6 +148,16 @@ class CoreRequestBuilder { } + /** + * Limit the request to the ActorId + * + * @param IQueryBuilder $qb + */ + protected function limitToPublic(IQueryBuilder &$qb) { + $this->limitToDBFieldInt($qb, 'public', 1); + } + + /** * Limit the request to the ActorId * @@ -191,6 +215,38 @@ class CoreRequestBuilder { } + /** + * Limit the request to the creation + * + * @param IQueryBuilder $qb + * @param int $delay + * + * @throws Exception + */ + protected function limitToCreation(IQueryBuilder &$qb, int $delay = 0) { + $date = new DateTime('now'); + $date->sub(new DateInterval('PT' . $delay . 'M')); + + $this->limitToDBFieldDateTime($qb, 'creation', $date); + } + + + /** + * Limit the request to the creation + * + * @param IQueryBuilder $qb + * @param int $delay + * + * @throws Exception + */ + protected function limitToCaching(IQueryBuilder &$qb, int $delay = 0) { + $date = new DateTime('now'); + $date->sub(new DateInterval('PT' . $delay . 'M')); + + $this->limitToDBFieldDateTime($qb, 'caching', $date); + } + + /** * Limit the request to the url * @@ -299,7 +355,7 @@ class CoreRequestBuilder { * @param bool $cs - case sensitive * @param string $alias */ - private function limitToDBField( + protected function limitToDBField( IQueryBuilder &$qb, string $field, string $value, bool $cs = true, string $alias = '' ) { $expr = $qb->expr(); @@ -326,7 +382,7 @@ class CoreRequestBuilder { * @param string $field * @param int $value */ - private function limitToDBFieldInt(IQueryBuilder &$qb, string $field, int $value) { + protected function limitToDBFieldInt(IQueryBuilder &$qb, string $field, int $value) { $expr = $qb->expr(); $pf = ($qb->getType() === QueryBuilder::SELECT) ? $this->defaultSelectAlias . '.' : ''; $field = $pf . $field; @@ -335,12 +391,42 @@ class CoreRequestBuilder { } + /** + * @param IQueryBuilder $qb + * @param string $field + */ + protected function limitToDBFieldEmpty(IQueryBuilder &$qb, string $field) { + $expr = $qb->expr(); + $pf = ($qb->getType() === QueryBuilder::SELECT) ? $this->defaultSelectAlias . '.' : ''; + $field = $pf . $field; + + $qb->andWhere($expr->eq($field, $qb->createNamedParameter(''))); + } + + + /** + * @param IQueryBuilder $qb + * @param string $field + * @param DateTime $date + */ + protected function limitToDBFieldDateTime(IQueryBuilder &$qb, string $field, DateTime $date) { + $expr = $qb->expr(); + $pf = ($qb->getType() === QueryBuilder::SELECT) ? $this->defaultSelectAlias . '.' : ''; + $field = $pf . $field; + + $orX = $expr->orX(); + $orX->add($expr->lte($field, $qb->createNamedParameter($date, IQueryBuilder::PARAM_DATE))); + $orX->add($expr->isNull($field)); + $qb->andWhere($orX); + } + + /** * @param IQueryBuilder $qb * @param string $field * @param array $values */ - private function limitToDBFieldArray(IQueryBuilder &$qb, string $field, array $values) { + protected function limitToDBFieldArray(IQueryBuilder &$qb, string $field, array $values) { $expr = $qb->expr(); $pf = ($qb->getType() === QueryBuilder::SELECT) ? $this->defaultSelectAlias . '.' : ''; $field = $pf . $field; @@ -363,7 +449,7 @@ class CoreRequestBuilder { * @param string $field * @param string $value */ - private function searchInDBField(IQueryBuilder &$qb, string $field, string $value) { + protected function searchInDBField(IQueryBuilder &$qb, string $field, string $value) { $expr = $qb->expr(); $pf = ($qb->getType() === QueryBuilder::SELECT) ? $this->defaultSelectAlias . '.' : ''; @@ -425,7 +511,7 @@ class CoreRequestBuilder { } $actor = new Person(); - $actor->import($new); + $actor->importFromDatabase($new); if ($actor->getType() !== Person::TYPE) { throw new InvalidResourceException(); @@ -434,6 +520,62 @@ class CoreRequestBuilder { return $actor; } + + /** + * @param IQueryBuilder $qb + * @param string $fieldDocumentId + */ + protected function leftJoinCacheDocuments(IQueryBuilder &$qb, string $fieldDocumentId) { + if ($qb->getType() !== QueryBuilder::SELECT) { + return; + } + + $expr = $qb->expr(); + $pf = $this->defaultSelectAlias; + +// /** @noinspection PhpMethodParametersCountMismatchInspection */ + $qb->selectAlias('cd.id', 'cachedocument_id') + ->selectAlias('cd.type', 'cachedocument_type') + ->selectAlias('cd.mime_type', 'cachedocument_mime_type') + ->selectAlias('cd.media_type', 'cachedocument_media_type') + ->selectAlias('cd.url', 'cachedocument_url') + ->selectAlias('cd.local_copy', 'cachedocument_local_copy') + ->selectAlias('cd.caching', 'cachedocument_caching') + ->selectAlias('cd.public', 'cachedocument_public') + ->selectAlias('cd.error', 'cachedocument_error') + ->selectAlias('ca.creation', 'cachedocument_creation') + ->leftJoin( + $this->defaultSelectAlias, CoreRequestBuilder::TABLE_CACHE_DOCUMENTS, 'cd', + $expr->eq($pf . '.' . $fieldDocumentId, 'cd.id') + ); + } + + + /** + * @param array $data + * + * @return Document + * @throws InvalidResourceException + */ + protected function parseCacheDocumentsLeftJoin(array $data): Document { + $new = []; + + foreach ($data as $k => $v) { + if (substr($k, 0, 14) === 'cachedocument_') { + $new[substr($k, 14)] = $v; + } + } + $document = new Document(); + + $document->importFromDatabase($new); + + if ($document->getType() !== Image::TYPE) { + throw new InvalidResourceException(); + } + + return $document; + } + } diff --git a/lib/Db/NotesRequestBuilder.php b/lib/Db/NotesRequestBuilder.php index c4444eea..16c5999d 100644 --- a/lib/Db/NotesRequestBuilder.php +++ b/lib/Db/NotesRequestBuilder.php @@ -160,6 +160,7 @@ class NotesRequestBuilder extends CoreRequestBuilder { protected function parseNotesSelectSql($data): Note { $dTime = new DateTime($this->get('published_time', $data, 'yesterday')); + // TODO - use $note->importFromDatabase() ? $note = new Note(); $note->setId($data['id']) ->setTo($data['to']) diff --git a/lib/Exceptions/CacheContentException.php b/lib/Exceptions/CacheContentException.php new file mode 100644 index 00000000..7ef15f28 --- /dev/null +++ b/lib/Exceptions/CacheContentException.php @@ -0,0 +1,8 @@ +getUrlCloud() === '') { + throw new UrlCloudException(); + } + + if ($base !== '') { + $base = $this->withoutEndSlash($this->withBeginSlash($base)); + } + $uuid = sprintf( '%04x%04x-%04x-%04x-%04x-%04x%04x%04x', mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xfff) | 0x4000, mt_rand(0, 0x3fff) | 0x8000, mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff) ); - $this->setId($base . '/' . $uuid); + $this->setId($this->getUrlCloud() . $base . '/' . $uuid); } /** @@ -178,9 +200,10 @@ abstract class ACore implements JsonSerializable { * @return ACore */ public function setType(string $type): ACore { - if ($type !== '') { - $this->type = $type; - } +// if ($type !== '') { + $this->type = $type; + +// } return $this; } @@ -343,8 +366,8 @@ abstract class ACore implements JsonSerializable { /** * @return string */ - public function getUrlRoot(): string { - return $this->root; + public function getUrlSocial(): string { + return $this->urlSocial; } /** @@ -352,8 +375,27 @@ abstract class ACore implements JsonSerializable { * * @return ACore */ - public function setUrlRoot(string $path): ACore { - $this->root = $path; + public function setUrlSocial(string $path): ACore { + $this->urlSocial = $path; + + return $this; + } + + + /** + * @return string + */ + public function getUrlCloud(): string { + return $this->urlCloud; + } + + /** + * @param string $path + * + * @return ACore + */ + public function setUrlCloud(string $path): ACore { + $this->urlCloud = $path; return $this; } @@ -568,6 +610,36 @@ abstract class ACore implements JsonSerializable { } + /** + * @return bool + */ + public function gotIcon(): bool { + if ($this->icon === null) { + return false; + } + + return true; + } + + /** + * @return Document + */ + public function getIcon(): Document { + return $this->icon; + } + + /** + * @param Document $icon + * + * @return ACore + */ + public function setIcon(Document $icon): ACore { + $this->icon = $icon; + + return $this; + } + + /** * @return bool */ @@ -623,7 +695,6 @@ abstract class ACore implements JsonSerializable { return $this; } - return $this->getParent() ->getRoot($chain); } @@ -792,6 +863,23 @@ abstract class ACore implements JsonSerializable { * @param array $data */ public function import(array $data) { + $this->setId($this->get('id', $data, '')); + $this->setType($this->get('type', $data, '')); + $this->setUrl($this->get('url', $data, '')); + $this->setSummary($this->get('summary', $data, '')); + $this->setToArray($this->getArray('to', $data, [])); + $this->setCcArray($this->getArray('cc', $data, [])); + $this->setPublished($this->get('published', $data, '')); + $this->setActorId($this->get('actor', $data, '')); + $this->setObjectId($this->get('object', $data, '')); + $this->setLocal(($this->getInt('local', $data, 0) === 1)); + } + + + /** + * @param array $data + */ + public function importFromDatabase(array $data) { $this->setId($this->get('id', $data, '')); $this->setType($this->get('type', $data, '')); $this->setUrl($this->get('url', $data, '')); @@ -821,7 +909,7 @@ abstract class ACore implements JsonSerializable { $this->addEntry('id', $this->getId()); $this->addEntry('type', $this->getType()); - $this->addEntry('url', $this->getId()); + $this->addEntry('url', $this->getUrl()); $this->addEntry('to', $this->getTo()); $this->addEntryArray('to', $this->getToArray()); @@ -850,11 +938,17 @@ abstract class ACore implements JsonSerializable { $this->addEntry('object', $this->getObjectId()); } + if ($this->gotIcon()) { + $this->addEntryItem('icon', $this->getIcon()); + } + if ($this->isCompleteDetails()) { $this->addEntry('source', $this->getSource()); } - $this->addEntryBool('local', $this->isLocal()); + if ($this->isLocal()) { + $this->addEntryBool('local', $this->isLocal()); + } return $this->getEntries(); } diff --git a/lib/Model/ActivityPub/Cache/CacheActor.php b/lib/Model/ActivityPub/Cache/CacheActor.php deleted file mode 100644 index 27768ae5..00000000 --- a/lib/Model/ActivityPub/Cache/CacheActor.php +++ /dev/null @@ -1,157 +0,0 @@ - - * @copyright 2018, Maxence Lange - * @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 . - * - */ - -namespace OCA\Social\Model\ActivityPub\Cache; - - -class CacheActor { - - /** @var int */ - private $id; - - /** @var string */ - private $account = ''; - - /** @var string */ - private $url; - - /** @var array */ - private $actor = []; - - /** @var int */ - private $creation = 0; - - - /** - * CacheActor constructor. - * - * @param int $id - */ - public function __construct($id = 0) { - $this->id = $id; - } - - - /** - * @return int - */ - public function getId(): int { - return $this->id; - } - - /** - * @param int $id - * - * @return CacheActor - */ - public function setId(int $id): CacheActor { - $this->account = $id; - - return $this; - } - - - /** - * @return string - */ - public function getAccount(): string { - return $this->account; - } - - /** - * @param string $account - * - * @return CacheActor - */ - public function setAccount(string $account): CacheActor { - $this->account = $account; - - return $this; - } - - - /** - * @return string - */ - public function getUrl(): string { - return $this->url; - } - - /** - * @param string $url - * - * @return CacheActor - */ - public function setUrl(string $url): CacheActor { - $this->url = $url; - - return $this; - } - - - /** - * @return array - */ - public function getActor(): array { - return $this->actor; - } - - /** - * @param array $actor - * - * @return CacheActor - */ - public function setActor(array $actor): CacheActor { - $this->actor = $actor; - - return $this; - } - - - /** - * @return int - */ - public function getCreation(): int { - return $this->creation; - } - - /** - * @param int $creation - * - * @return CacheActor - */ - public function setCreation(int $creation): CacheActor { - $this->creation = $creation; - - return $this; - } - - -} - diff --git a/lib/Model/ActivityPub/Document.php b/lib/Model/ActivityPub/Document.php new file mode 100644 index 00000000..c7da00f8 --- /dev/null +++ b/lib/Model/ActivityPub/Document.php @@ -0,0 +1,246 @@ + + * @copyright 2018, Maxence Lange + * @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 . + * + */ + + +namespace OCA\Social\Model\ActivityPub; + + +use DateTime; +use JsonSerializable; +use OCA\Social\Exceptions\UrlCloudException; + + +/** + * Class Document + * + * @package OCA\Social\Model\ActivityPub + */ +class Document extends ACore implements JsonSerializable { + + + const TYPE = 'Document'; + + + /** @var string */ + private $mediaType = ''; + + /** @var string */ + private $mimeType = ''; + + /** @var string */ + private $localCopy = ''; + + /** @var int */ + private $caching = 0; + + /** @var bool */ + private $public = false; + + /** @var int */ + private $error = 0; + + + /** + * Document constructor. + * + * @param ACore $parent + */ + public function __construct($parent = null) { + parent::__construct($parent); + + $this->setType(self::TYPE); + } + + + /** + * @return string + */ + public function getMediaType(): string { + return $this->mediaType; + } + + /** + * @param string $mediaType + * + * @return ACore + */ + public function setMediaType(string $mediaType): ACore { + $this->mediaType = $mediaType; + + return $this; + } + + + /** + * @return string + */ + public function getMimeType(): string { + return $this->mimeType; + } + + /** + * @param string $mimeType + * + * @return ACore + */ + public function setMimeType(string $mimeType): ACore { + $this->mimeType = $mimeType; + + return $this; + } + + + /** + * @return string + */ + public function getLocalCopy(): string { + return $this->localCopy; + } + + /** + * @param string $localCopy + * + * @return Document + */ + public function setLocalCopy(string $localCopy): Document { + $this->localCopy = $localCopy; + + return $this; + } + + + /** + * @return bool + */ + public function isPublic(): bool { + return $this->public; + } + + /** + * @param bool $public + * + * @return Document + */ + public function setPublic(bool $public): Document { + $this->public = $public; + + return $this; + } + + + /** + * @return int + */ + public function getError(): int { + return $this->error; + } + + /** + * @param int $error + * + * @return Document + */ + public function setError(int $error): Document { + $this->error = $error; + + return $this; + } + + + /** + * @return int + */ + public function getCaching(): int { + return $this->caching; + } + + /** + * @param int $caching + * + * @return Document + */ + public function setCaching(int $caching): Document { + $this->caching = $caching; + + return $this; + } + + + /** + * @param array $data + * + * @throws UrlCloudException + */ + public function import(array $data) { + parent::import($data); + + $this->setMediaType($this->get('mediaType', $data, '')); + + if ($this->getId() === '') { + $this->generateUniqueId('/documents/g'); + } + } + + + /** + * @param array $data + */ + public function importFromDatabase(array $data) { + parent::importFromDatabase($data); + + $this->setPublic(($this->getInt('public', $data, 0) === 1) ? true : false); + $this->setError($this->getInt('error', $data, 0)); + $this->setLocalCopy($this->get('local_copy', $data, '')); + $this->setMediaType($this->get('media_type', $data, '')); + $this->setMimeType($this->get('mime_type', $data, '')); + + if ($this->get('caching', $data, '') === '') { + $this->setCaching(0); + } else { + $date = new DateTime($this->get('caching', $data, '')); + $this->setCaching($date->getTimestamp()); + } + } + + /** + * @return array + */ + public function jsonSerialize(): array { + return array_merge( + parent::jsonSerialize(), + [ + 'mediaType' => $this->getMediaType(), + 'mimeType' => $this->getMimeType(), + 'localCopy' => $this->getLocalCopy() + ] + ); + } + +} + diff --git a/lib/Model/ActivityPub/Image.php b/lib/Model/ActivityPub/Image.php new file mode 100644 index 00000000..5947c4a6 --- /dev/null +++ b/lib/Model/ActivityPub/Image.php @@ -0,0 +1,83 @@ + + * @copyright 2018, Maxence Lange + * @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 . + * + */ + + +namespace OCA\Social\Model\ActivityPub; + + +use JsonSerializable; +use OCA\Social\Exceptions\UrlCloudException; + + +/** + * Class Image + * + * @package OCA\Social\Model\ActivityPub + */ +class Image extends Document implements JsonSerializable { + + + const TYPE = 'Image'; + + + /** + * Image constructor. + * + * @param ACore $parent + */ + public function __construct($parent = null) { + parent::__construct($parent); + + $this->setType(self::TYPE); + } + + + /** + * @param array $data + * + * @throws UrlCloudException + */ + public function import(array $data) { + parent::import($data); + } + + + /** + * @return array + */ + public function jsonSerialize(): array { + return array_merge( + parent::jsonSerialize(), + [ + ] + ); + } + +} + diff --git a/lib/Model/ActivityPub/Note.php b/lib/Model/ActivityPub/Note.php index 7a71493c..53b91c06 100644 --- a/lib/Model/ActivityPub/Note.php +++ b/lib/Model/ActivityPub/Note.php @@ -199,7 +199,6 @@ class Note extends ACore implements JsonSerializable { public function import(array $data) { parent::import($data); - $this->setSummary($this->get('summary', $data, '')); $this->setInReplyTo($this->get('inReplyTo', $data, '')); $this->setAttributedTo($this->get('attributedTo', $data, '')); $this->setSensitive($this->getBool('sensitive', $data, false)); @@ -219,7 +218,7 @@ class Note extends ACore implements JsonSerializable { parent::jsonSerialize(), [ 'content' => $this->getContent(), - 'attributedTo' => $this->getUrlRoot() . $this->getAttributedTo(), + 'attributedTo' => $this->getUrlSocial() . $this->getAttributedTo(), 'inReplyTo' => $this->getInReplyTo(), 'sensitive' => $this->isSensitive(), 'conversation' => $this->getConversation() diff --git a/lib/Model/ActivityPub/Person.php b/lib/Model/ActivityPub/Person.php index 18c73be6..41f3b375 100644 --- a/lib/Model/ActivityPub/Person.php +++ b/lib/Model/ActivityPub/Person.php @@ -32,6 +32,7 @@ namespace OCA\Social\Model\ActivityPub; use JsonSerializable; +use OCA\Social\Exceptions\UrlCloudException; /** @@ -340,9 +341,44 @@ class Person extends ACore implements JsonSerializable { /** * @param array $data + * + * @throws UrlCloudException */ public function import(array $data) { parent::import($data); + $this->setPreferredUsername($this->get('preferredUsername', $data, '')) + ->setPublicKey($this->get('publicKey.publicKeyPem', $data)) + ->setSharedInbox($this->get('endpoints.sharedInbox', $data)) + ->setName($this->get('name', $data, '')) + ->setAccount($this->get('account', $data, '')) + ->setInbox($this->get('inbox', $data, '')) + ->setOutbox($this->get('outbox', $data, '')) + ->setFollowers($this->get('followers', $data, '')) + ->setFollowing($this->get('following', $data, '')) + ->setFeatured($this->get('featured', $data, '')); + + $icon = new Image($this); + $icon->setUrlCloud($this->getUrlCloud()); + $icon->import($this->getArray('icon', $data, [])); + + if ($icon->getType() === Image::TYPE) { + $this->setIcon($icon); + } + + +// ->setCreation($this->getInt('creation', $data, 0)); + +// if ($this->getPreferredUsername() === '') { +// $this->setType('Invalid'); +// } + } + + + /** + * @param array $data + */ + public function importFromDatabase(array $data) { + parent::importFromDatabase($data); $this->setPreferredUsername($this->get('preferred_username', $data, '')) ->setName($this->get('name', $data, '')) ->setAccount($this->get('account', $data, '')) @@ -370,8 +406,8 @@ class Person extends ACore implements JsonSerializable { parent::jsonSerialize(), [ 'aliases' => [ - $this->getUrlRoot() . '@' . $this->getPreferredUsername(), - $this->getUrlRoot() . 'users/' . $this->getPreferredUsername() + $this->getUrlSocial() . '@' . $this->getPreferredUsername(), + $this->getUrlSocial() . 'users/' . $this->getPreferredUsername() ], 'preferredUsername' => $this->getPreferredUsername(), 'name' => $this->getName(), diff --git a/lib/Service/ActivityPub/DocumentService.php b/lib/Service/ActivityPub/DocumentService.php new file mode 100644 index 00000000..134ff5c7 --- /dev/null +++ b/lib/Service/ActivityPub/DocumentService.php @@ -0,0 +1,161 @@ + + * @copyright 2018, Maxence Lange + * @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 . + * + */ + + +namespace OCA\Social\Service\ActivityPub; + + +use Exception; +use OCA\Social\Db\CacheDocumentsRequest; +use OCA\Social\Exceptions\CacheContentException; +use OCA\Social\Exceptions\CacheContentSizeException; +use OCA\Social\Exceptions\CacheDocumentDoesNotExistException; +use OCA\Social\Model\ActivityPub\ACore; +use OCA\Social\Model\ActivityPub\Document; +use OCA\Social\Service\CacheService; +use OCA\Social\Service\MiscService; +use OCP\Files\NotPermittedException; +use OCP\Files\SimpleFS\ISimpleFile; + + +class DocumentService implements ICoreService { + + + /** @var CacheDocumentsRequest */ + private $cacheDocumentsRequest; + + /** @var CacheService */ + private $cacheService; + + /** @var MiscService */ + private $miscService; + + + /** + * DocumentService constructor. + * + * @param CacheDocumentsRequest $cacheDocumentsRequest + * @param CacheService $cacheService + * @param MiscService $miscService + */ + public function __construct( + CacheDocumentsRequest $cacheDocumentsRequest, CacheService $cacheService, + MiscService $miscService + ) { + $this->cacheDocumentsRequest = $cacheDocumentsRequest; + $this->cacheService = $cacheService; + $this->miscService = $miscService; + } + + + /** + * @param string $id + * @param bool $public + * + * @return Document + * @throws CacheDocumentDoesNotExistException + * @throws NotPermittedException + */ + public function cacheRemoteDocument(string $id, bool $public = false) { + $document = $this->cacheDocumentsRequest->getById($id, $public); + if ($document->getLocalCopy() !== '') { + return $document; + } + + // TODO - ignore this if getCaching is older than 15 minutes + if ($document->getCaching() > (time() - (CacheDocumentsRequest::CACHE_TTL * 60))) { + return $document; + } + + $this->cacheDocumentsRequest->initCaching($document); + + try { + $localCopy = $this->cacheService->saveRemoteFileToCache($document->getUrl(), $mime); + $document->setMimeType($mime); + $document->setLocalCopy($localCopy); + $this->cacheDocumentsRequest->endCaching($document); + } catch (CacheContentSizeException $e) { + $this->cacheDocumentsRequest->endCaching($document); + } catch (CacheContentException $e) { + } + + return $document; + } + + + /** + * @param string $id + * + * @param bool $public + * + * @return ISimpleFile + * @throws CacheContentException + * @throws CacheDocumentDoesNotExistException + * @throws NotPermittedException + */ + public function getFromCache(string $id, bool $public = false) { + $document = $this->cacheRemoteDocument($id, $public); + + return $this->cacheService->getContentFromCache($document->getLocalCopy()); + } + + + /** + * @return int + * @throws CacheDocumentDoesNotExistException + * @throws NotPermittedException + * @throws Exception + */ + public function manageCacheDocuments(): int { + $update = $this->cacheDocumentsRequest->getNotCachedDocuments(); + + foreach ($update as $item) { + $this->cacheRemoteDocument($item->getId()); + } + + return sizeof($update); + } + + + /** + * @param ACore $item + */ + public function parse(ACore $item) { + // TODO: Implement parse() method. + } + + /** + * @param ACore $item + */ + public function delete(ACore $item) { + // TODO: Implement delete() method. + } + +} + diff --git a/lib/Service/ActivityPub/FollowService.php b/lib/Service/ActivityPub/FollowService.php index 43871642..36423fbc 100644 --- a/lib/Service/ActivityPub/FollowService.php +++ b/lib/Service/ActivityPub/FollowService.php @@ -34,7 +34,9 @@ namespace OCA\Social\Service\ActivityPub; use Exception; use OCA\Social\Db\FollowsRequest; use OCA\Social\Exceptions\ActorDoesNotExistException; +use OCA\Social\Exceptions\CacheActorDoesNotExistException; use OCA\Social\Exceptions\FollowDoesNotExistException; +use OCA\Social\Exceptions\InvalidResourceException; use OCA\Social\Exceptions\RequestException; use OCA\Social\Exceptions\SocialAppConfigException; use OCA\Social\Model\ActivityPub\ACore; @@ -96,14 +98,17 @@ class FollowService implements ICoreService { * @param Person $actor * @param string $account * + * @throws ActorDoesNotExistException * @throws RequestException * @throws SocialAppConfigException - * @throws ActorDoesNotExistException + * @throws CacheActorDoesNotExistException + * @throws InvalidResourceException */ public function followAccount(Person $actor, string $account) { $remoteActor = $this->personService->getFromAccount($account); $follow = new Follow(); - $follow->generateUniqueId($this->configService->getCloudAddress()); + $follow->setUrlCloud($this->configService->getCloudAddress()); + $follow->generateUniqueId(); $follow->setActorId($actor->getId()); $follow->setObjectId($remoteActor->getId()); @@ -124,6 +129,8 @@ class FollowService implements ICoreService { * @param Person $actor * @param string $account * + * @throws CacheActorDoesNotExistException + * @throws InvalidResourceException * @throws RequestException */ public function unfollowAccount(Person $actor, string $account) { diff --git a/lib/Service/ActivityPub/NoteService.php b/lib/Service/ActivityPub/NoteService.php index 2adecf1c..7107c1fe 100644 --- a/lib/Service/ActivityPub/NoteService.php +++ b/lib/Service/ActivityPub/NoteService.php @@ -126,7 +126,7 @@ class NoteService implements ICoreService { $note->setId($this->configService->generateId('@' . $actor->getPreferredUsername())); $note->setPublished(date("c")); $note->setAttributedTo( - $this->configService->getUrlRoot() . '@' . $actor->getPreferredUsername() + $this->configService->getUrlSocial() . '@' . $actor->getPreferredUsername() ); $this->setRecipient($note, $actor, $type); diff --git a/lib/Service/ActivityPub/PersonService.php b/lib/Service/ActivityPub/PersonService.php index a4d4eee1..432ab2a9 100644 --- a/lib/Service/ActivityPub/PersonService.php +++ b/lib/Service/ActivityPub/PersonService.php @@ -34,11 +34,16 @@ namespace OCA\Social\Service\ActivityPub; use daita\MySmallPhpTools\Traits\TArrayTools; use Exception; use OCA\Social\Db\CacheActorsRequest; +use OCA\Social\Db\CacheDocumentsRequest; use OCA\Social\Exceptions\CacheActorDoesNotExistException; +use OCA\Social\Exceptions\CacheDocumentDoesNotExistException; use OCA\Social\Exceptions\InvalidResourceException; use OCA\Social\Exceptions\RequestException; +use OCA\Social\Exceptions\SocialAppConfigException; +use OCA\Social\Exceptions\UrlCloudException; use OCA\Social\Model\ActivityPub\ACore; use OCA\Social\Model\ActivityPub\Person; +use OCA\Social\Service\ConfigService; use OCA\Social\Service\ICoreService; use OCA\Social\Service\InstanceService; use OCA\Social\Service\MiscService; @@ -58,9 +63,15 @@ class PersonService implements ICoreService { /** @var CacheActorsRequest */ private $cacheActorsRequest; + /** @var CacheDocumentsRequest */ + private $cacheDocumentsRequest; + /** @var InstanceService */ private $instanceService; + /** @var ConfigService */ + private $configService; + /** @var MiscService */ private $miscService; @@ -69,15 +80,19 @@ class PersonService implements ICoreService { * UndoService constructor. * * @param CacheActorsRequest $cacheActorsRequest + * @param CacheDocumentsRequest $cacheDocumentsRequest * @param InstanceService $instanceService + * @param ConfigService $configService * @param MiscService $miscService */ public function __construct( - CacheActorsRequest $cacheActorsRequest, InstanceService $instanceService, - MiscService $miscService + CacheActorsRequest $cacheActorsRequest, CacheDocumentsRequest $cacheDocumentsRequest, + InstanceService $instanceService, ConfigService $configService, MiscService $miscService ) { $this->cacheActorsRequest = $cacheActorsRequest; + $this->cacheDocumentsRequest = $cacheDocumentsRequest; $this->instanceService = $instanceService; + $this->configService = $configService; $this->miscService = $miscService; } @@ -108,6 +123,8 @@ class PersonService implements ICoreService { * @return Person * @throws InvalidResourceException * @throws RequestException + * @throws SocialAppConfigException + * @throws UrlCloudException */ public function getFromId(string $id, bool $refresh = false): Person { @@ -125,19 +142,8 @@ class PersonService implements ICoreService { $actor = $this->cacheActorsRequest->getFromId($id); } catch (CacheActorDoesNotExistException $e) { $object = $this->instanceService->retrieveObject($id); - $actor = new Person(); - $actor->import($object); - $actor->setSource(json_encode($object, JSON_UNESCAPED_SLASHES)); - - $actor->setPreferredUsername($this->get('preferredUsername', $object, '')); - $actor->setPublicKey($this->get('publicKey.publicKeyPem', $object)); - $actor->setSharedInbox($this->get('endpoints.sharedInbox', $object)); + $actor = $this->generateActorFromObject($object); $actor->setAccount($actor->getPreferredUsername() . '@' . $this->get('_host', $object)); - - if ($actor->getType() !== Person::TYPE) { - throw new InvalidResourceException(); - } - try { $this->parse($actor); } catch (Exception $e) { @@ -158,6 +164,8 @@ class PersonService implements ICoreService { * @throws InvalidResourceException * @throws RequestException * @throws CacheActorDoesNotExistException + * @throws SocialAppConfigException + * @throws UrlCloudException */ public function getFromAccount(string $account, bool $retrieve = true): Person { @@ -169,19 +177,8 @@ class PersonService implements ICoreService { } $object = $this->instanceService->retrieveAccount($account); - - $actor = new Person(); - $actor->import($object); - + $actor = $this->generateActorFromObject($object); $actor->setAccount($account); - $actor->setPreferredUsername($this->get('preferredUsername', $object, '')); - $actor->setPublicKey($this->get('publicKey.publicKeyPem', $object)); - $actor->setSharedInbox($this->get('endpoints.sharedInbox', $object)); - - if ($actor->getType() !== Person::TYPE) { - throw new InvalidResourceException(); - } - try { $this->parse($actor); } catch (Exception $e) { @@ -193,6 +190,40 @@ class PersonService implements ICoreService { } + /** + * @param array $object + * + * @return Person + * @throws InvalidResourceException + * @throws SocialAppConfigException + * @throws UrlCloudException + */ + private function generateActorFromObject(array $object) { + + $actor = new Person(); + $actor->setUrlCloud($this->configService->getCloudAddress()); + $actor->import($object); + + if ($actor->getType() !== Person::TYPE) { + throw new InvalidResourceException(); + } + + $actor->setSource(json_encode($object, JSON_UNESCAPED_SLASHES)); +// $actor->setPreferredUsername($this->get('preferredUsername', $object, '')); +// $actor->setPublicKey($this->get('publicKey.publicKeyPem', $object)); +// $actor->setSharedInbox($this->get('endpoints.sharedInbox', $object)); +// $actor->setAccount($actor->getPreferredUsername() . '@' . $this->get('_host', $object)); +// +// $icon = new Image($actor); +// $icon->setUrlCloud($this->configService->getCloudAddress()); +// $icon->import($this->getArray('icon', $object, [])); +// if ($icon->getType() === Image::TYPE) { +// $actor->setIcon($icon); +// } +// + return $actor; + } + /** * @param string $search * @@ -216,10 +247,40 @@ class PersonService implements ICoreService { return; } + if ($person->gotIcon()) { + try { + $icon = $this->cacheDocumentsRequest->getBySource( + $person->getIcon() + ->getUrl() + ); + $person->setIcon($icon); + } catch (CacheDocumentDoesNotExistException $e) { + $this->cacheDocumentsRequest->save($person->getIcon()); + } + } + $this->cacheActorsRequest->save($person); } + /** + * @throws Exception + * @return int + */ + public function manageCacheRemoteActors(): int { + $update = $this->cacheActorsRequest->getRemoteActorsToUpdate(); + + foreach ($update as $item) { + try { + $this->getFromId($item->getId(), true); + } catch (Exception $e) { + } + } + + return sizeof($update); + } + + /** * @param ACore $item */ diff --git a/lib/Service/ActivityService.php b/lib/Service/ActivityService.php index 7f402221..ac87e854 100644 --- a/lib/Service/ActivityService.php +++ b/lib/Service/ActivityService.php @@ -309,7 +309,7 @@ class ActivityService { $localActor = $this->getActorFromItem($activity); $localActorLink = - $this->configService->getUrlRoot() . '@' . $localActor->getPreferredUsername(); + $this->configService->getUrlSocial() . '@' . $localActor->getPreferredUsername(); $signature = "(request-target): post " . $path->getPath() . "\nhost: " . $path->getAddress() . "\ndate: " . $date; diff --git a/lib/Service/ActorService.php b/lib/Service/ActorService.php index 25758d89..348d1a1f 100644 --- a/lib/Service/ActorService.php +++ b/lib/Service/ActorService.php @@ -128,18 +128,6 @@ class ActorService { } - /** - * @param string $search - * - * @deprecated - used !? - * @return Person[] - * @throws SocialAppConfigException - */ - public function searchLocalAccounts(string $search): array { - return $this->actorsRequest->searchFromUsername($search); - } - - /** * Method should be called by the frontend and will generate a fresh Social account for * the user, using the userId and the username. @@ -234,4 +222,22 @@ class ActorService { } + /** + * @throws Exception + * @return int + */ + public function manageCacheLocalActors(): int { + $update = $this->actorsRequest->getAll(); + + foreach ($update as $item) { + try { + $this->cacheLocalActorByUsername($item->getPreferredUsername(), true); + } catch (Exception $e) { + } + } + + return sizeof($update); + } + + } diff --git a/lib/Service/CacheService.php b/lib/Service/CacheService.php new file mode 100644 index 00000000..60c5827a --- /dev/null +++ b/lib/Service/CacheService.php @@ -0,0 +1,170 @@ + + * @copyright 2018, Maxence Lange + * @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 . + * + */ + +namespace OCA\Social\Service; + + +use Exception; +use OCA\Social\Exceptions\CacheContentException; +use OCA\Social\Exceptions\CacheContentSizeException; +use OCP\Files\IAppData; +use OCP\Files\NotFoundException; +use OCP\Files\NotPermittedException; +use OCP\Files\SimpleFS\ISimpleFile; + + +class CacheService { + + + const ERROR_MAX_SIZE = 1; + + + /** @var IAppData */ + private $appData; + + /** @var ConfigService */ + private $configService; + + /** @var MiscService */ + private $miscService; + + + /** + * CacheService constructor. + * + * @param IAppData $appData + * @param ConfigService $configService + * @param MiscService $miscService + */ + public function __construct( + IAppData $appData, ConfigService $configService, MiscService $miscService + ) { + $this->appData = $appData; + $this->configService = $configService; + $this->miscService = $miscService; + } + + + /** + * @param string $url + * + * @param string $mime + * + * @return string + * @throws CacheContentException + * @throws NotPermittedException + * @throws CacheContentSizeException + */ + public function saveRemoteFileToCache(string $url, &$mime = '') { + + $filename = sprintf( + '%04x%04x-%04x-%04x-%04x-%04x%04x%04x', mt_rand(0, 0xffff), mt_rand(0, 0xffff), + mt_rand(0, 0xffff), mt_rand(0, 0xfff) | 0x4000, mt_rand(0, 0x3fff) | 0x8000, + mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff) + ); + + // creating a path aa/bb/cc/dd/ from the filename aabbccdd-0123-[...] + $path = chunk_split(substr($filename, 0, 8), 2, '/'); + + try { + $folder = $this->appData->getFolder($path); + } catch (NotFoundException $e) { + $folder = $this->appData->newFolder($path); + } + + $content = $this->retrieveContent($url); + + // TODO - get mime type in a better way. + // To get the mime type, we create a temp file + $tmpFile = tmpfile(); + $tmpPath = stream_get_meta_data($tmpFile)['uri']; + fwrite($tmpFile, $content); + $mime = mime_content_type($tmpPath); + fclose($tmpFile); + + $cache = $folder->newFile($filename); + $cache->putContent($content); + + return $path . $filename; + } + + + /** + * @param string $path + * + * @return ISimpleFile + * @throws CacheContentException + */ + public function getContentFromCache(string $path) { + + $pos = strrpos($path, '/'); + $dir = substr($path, 0, $pos); + $filename = substr($path, $pos + 1); + + try { + $file = $this->appData->getFolder($dir) + ->getFile($filename); + + return $file; + } catch (Exception $e) { + throw new CacheContentException(); + } + } + + + /** + * @param string $url + * + * @return string + * @throws CacheContentException + * @throws CacheContentSizeException + */ + public function retrieveContent(string $url) { + $maxSize = + $this->configService->getAppValueInt(ConfigService::SOCIAL_MAX_SIZE) * 1024 * 1024; + + $fd = fopen($url, "r"); + if ($fd === false) { + throw new CacheContentException(); + } + + $content = ''; + while (!feof($fd)) { + $content .= fread($fd, 4096); + if (strlen($content) > $maxSize) { + throw new CacheContentSizeException(); + } + } + fclose($fd); + + return $content; + } + + +} diff --git a/lib/Service/ConfigService.php b/lib/Service/ConfigService.php index 4ecaa335..34d4bb16 100644 --- a/lib/Service/ConfigService.php +++ b/lib/Service/ConfigService.php @@ -52,10 +52,12 @@ class ConfigService { const SOCIAL_ADDRESS = 'address'; + const SOCIAL_MAX_SIZE = 'max_size'; /** @var array */ public $defaults = [ - self::SOCIAL_ADDRESS => '' + self::SOCIAL_ADDRESS => '', + self::SOCIAL_MAX_SIZE => 25 ]; /** @var string */ @@ -111,6 +113,22 @@ class ConfigService { return $this->config->getAppValue(Application::APP_NAME, $key, $defaultValue); } + /** + * Get a value by key + * + * @param string $key + * + * @return int + */ + public function getAppValueInt(string $key): int { + $defaultValue = null; + if (array_key_exists($key, $this->defaults)) { + $defaultValue = $this->defaults[$key]; + } + + return (int)$this->config->getAppValue(Application::APP_NAME, $key, $defaultValue); + } + /** * Set a value by key * @@ -211,13 +229,11 @@ class ConfigService { } /** - * @param bool $host + * @param string $cloudAddress * * @return string - * @throws SocialAppConfigException */ public function setCloudAddress(string $cloudAddress) { - // TODO: Validate $this->setAppValue(self::SOCIAL_ADDRESS, $cloudAddress); } @@ -249,10 +265,12 @@ class ConfigService { /** + * // TODO - check the Apps folder + * * @return string * @throws SocialAppConfigException */ - public function getUrlRoot(): string { + public function getUrlSocial(): string { return $this->getCloudAddress() . '/apps/social/'; } @@ -267,7 +285,7 @@ class ConfigService { public function generateId(string $path = '', $generateId = true): string { $path = $this->withoutBeginSlash($this->withEndSlash($path)); - $id = $this->getUrlRoot() . $path; + $id = $this->getUrlSocial() . $path; if ($generateId === true) { $id .= time() . crc32(uniqid()); } diff --git a/lib/Service/ImportService.php b/lib/Service/ImportService.php index 294c6acd..381fac1e 100644 --- a/lib/Service/ImportService.php +++ b/lib/Service/ImportService.php @@ -33,14 +33,18 @@ namespace OCA\Social\Service; use daita\MySmallPhpTools\Traits\TArrayTools; use Exception; +use OCA\Social\Exceptions\SocialAppConfigException; use OCA\Social\Exceptions\UnknownItemException; +use OCA\Social\Exceptions\UrlCloudException; use OCA\Social\Model\ActivityPub\ACore; use OCA\Social\Model\ActivityPub\Activity\Accept; use OCA\Social\Model\ActivityPub\Activity\Create; use OCA\Social\Model\ActivityPub\Activity\Delete; use OCA\Social\Model\ActivityPub\Activity\Reject; use OCA\Social\Model\ActivityPub\Activity\Tombstone; +use OCA\Social\Model\ActivityPub\Document; use OCA\Social\Model\ActivityPub\Follow; +use OCA\Social\Model\ActivityPub\Image; use OCA\Social\Model\ActivityPub\Note; use OCA\Social\Model\ActivityPub\Activity\Undo; use OCA\Social\Service\ActivityPub\DeleteService; @@ -67,6 +71,8 @@ class ImportService { /** @var DeleteService */ private $deleteService; + private $configService; + /** @var MiscService */ private $miscService; @@ -78,17 +84,18 @@ class ImportService { * @param UndoService $undoService * @param FollowService $followService * @param DeleteService $deleteService + * @param ConfigService $configService * @param MiscService $miscService */ public function __construct( NoteService $noteService, UndoService $undoService, FollowService $followService, - DeleteService $deleteService, - MiscService $miscService + DeleteService $deleteService, ConfigService $configService, MiscService $miscService ) { $this->noteService = $noteService; $this->undoService = $undoService; $this->followService = $followService; $this->deleteService = $deleteService; + $this->configService = $configService; $this->miscService = $miscService; } @@ -98,6 +105,8 @@ class ImportService { * * @return ACore * @throws UnknownItemException + * @throws UrlCloudException + * @throws SocialAppConfigException */ public function import(string $json) { $data = json_decode($json, true); @@ -113,6 +122,8 @@ class ImportService { * * @return ACore * @throws UnknownItemException + * @throws UrlCloudException + * @throws SocialAppConfigException */ private function createItem(array $data, $root = null): ACore { @@ -133,6 +144,10 @@ class ImportService { $item = new Note($root); break; + case Image::TYPE: + $item = new Image($root); + break; + case Follow::TYPE: $item = new Follow($root); break; @@ -153,6 +168,7 @@ class ImportService { throw new UnknownItemException(); } + $item->setUrlCloud($this->configService->getCloudAddress()); $item->import($data); $item->setSource(json_encode($data, JSON_UNESCAPED_SLASHES)); @@ -162,6 +178,13 @@ class ImportService { } catch (UnknownItemException $e) { } + try { + /** @var Document $icon */ + $icon = $this->createItem($this->getArray('icon', $data, []), $item); + $item->setIcon($icon); + } catch (UnknownItemException $e) { + } + return $item; } @@ -205,6 +228,10 @@ class ImportService { $service = $this->followService; break; +// case Image::TYPE: +// $service = $this->imageService; +// break; + case Note::TYPE: $service = $this->noteService; break;