From a449af64602a35148a2a5b6d08c89f97b254c0b4 Mon Sep 17 00:00:00 2001 From: Salvatore Martire <4652631+salmart-dev@users.noreply.github.com> Date: Mon, 29 Dec 2025 17:21:20 +0100 Subject: [PATCH] feat: introduce API for partial share providers Adds support for retrieval of shares by path Signed-off-by: Salvatore Martire <4652631+salmart-dev@users.noreply.github.com> --- lib/composer/composer/autoload_classmap.php | 1 + lib/composer/composer/autoload_static.php | 1 + lib/private/Share20/DefaultShareProvider.php | 110 ++++++++++++++++++- lib/private/Share20/Manager.php | 39 +++++++ lib/public/Share/IManager.php | 12 ++ lib/public/Share/IPartialShareProvider.php | 31 ++++++ 6 files changed, 193 insertions(+), 1 deletion(-) create mode 100644 lib/public/Share/IPartialShareProvider.php diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index 5d515c66dd3..7cca0c7a414 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -817,6 +817,7 @@ return array( 'OCP\\Share\\Exceptions\\ShareTokenException' => $baseDir . '/lib/public/Share/Exceptions/ShareTokenException.php', 'OCP\\Share\\IAttributes' => $baseDir . '/lib/public/Share/IAttributes.php', 'OCP\\Share\\IManager' => $baseDir . '/lib/public/Share/IManager.php', + 'OCP\\Share\\IPartialShareProvider' => $baseDir . '/lib/public/Share/IPartialShareProvider.php', 'OCP\\Share\\IProviderFactory' => $baseDir . '/lib/public/Share/IProviderFactory.php', 'OCP\\Share\\IPublicShareTemplateFactory' => $baseDir . '/lib/public/Share/IPublicShareTemplateFactory.php', 'OCP\\Share\\IPublicShareTemplateProvider' => $baseDir . '/lib/public/Share/IPublicShareTemplateProvider.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index d25b0f94870..c9fcc66a253 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -858,6 +858,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 'OCP\\Share\\Exceptions\\ShareTokenException' => __DIR__ . '/../../..' . '/lib/public/Share/Exceptions/ShareTokenException.php', 'OCP\\Share\\IAttributes' => __DIR__ . '/../../..' . '/lib/public/Share/IAttributes.php', 'OCP\\Share\\IManager' => __DIR__ . '/../../..' . '/lib/public/Share/IManager.php', + 'OCP\\Share\\IPartialShareProvider' => __DIR__ . '/../../..' . '/lib/public/Share/IPartialShareProvider.php', 'OCP\\Share\\IProviderFactory' => __DIR__ . '/../../..' . '/lib/public/Share/IProviderFactory.php', 'OCP\\Share\\IPublicShareTemplateFactory' => __DIR__ . '/../../..' . '/lib/public/Share/IPublicShareTemplateFactory.php', 'OCP\\Share\\IPublicShareTemplateProvider' => __DIR__ . '/../../..' . '/lib/public/Share/IPublicShareTemplateProvider.php', diff --git a/lib/private/Share20/DefaultShareProvider.php b/lib/private/Share20/DefaultShareProvider.php index 3104ac7eff7..d086c273972 100644 --- a/lib/private/Share20/DefaultShareProvider.php +++ b/lib/private/Share20/DefaultShareProvider.php @@ -32,6 +32,7 @@ use OCP\Mail\IMailer; use OCP\Share\Exceptions\ShareNotFound; use OCP\Share\IAttributes; use OCP\Share\IManager; +use OCP\Share\IPartialShareProvider; use OCP\Share\IShare; use OCP\Share\IShareProviderGetUsers; use OCP\Share\IShareProviderSupportsAccept; @@ -49,7 +50,8 @@ class DefaultShareProvider implements IShareProviderWithNotification, IShareProviderSupportsAccept, IShareProviderSupportsAllSharesInFolder, - IShareProviderGetUsers { + IShareProviderGetUsers, + IPartialShareProvider { public function __construct( private IDBConnection $dbConn, private IUserManager $userManager, @@ -953,6 +955,112 @@ class DefaultShareProvider implements return $shares; } + /** + * @inheritDoc + */ + public function getSharedWithByPath( + string $userId, + int $shareType, + string $path, + bool $forChildren, + int $limit, + int $offset, + ): iterable { + $shares = []; + + if ($shareType === IShare::TYPE_USER) { + //Get shares directly with this user + $qb = $this->dbConn->getQueryBuilder(); + $qb->select('s.*', + 'f.fileid', 'f.path', 'f.permissions AS f_permissions', 'f.storage', 'f.path_hash', + 'f.parent AS f_parent', 'f.name', 'f.mimetype', 'f.mimepart', 'f.size', 'f.mtime', 'f.storage_mtime', + 'f.encrypted', 'f.unencrypted_size', 'f.etag', 'f.checksum' + ) + ->selectAlias('st.id', 'storage_string_id') + ->from('share', 's') + ->leftJoin('s', 'filecache', 'f', $qb->expr()->eq('s.file_source', 'f.fileid')) + ->leftJoin('f', 'storages', 'st', $qb->expr()->eq('f.storage', 'st.numeric_id')); + + // Order by id + $qb->orderBy('s.id'); + + // Set limit and offset + if ($limit !== -1) { + $qb->setMaxResults($limit); + } + $qb->setFirstResult($offset); + + $qb->where($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_USER))) + ->andWhere($qb->expr()->eq('share_with', $qb->createNamedParameter($userId))) + ->andWhere($qb->expr()->in('item_type', $qb->createNamedParameter(['file', 'folder'], IQueryBuilder::PARAM_STR_ARRAY))); + + if ($forChildren) { + $qb->andWhere($qb->expr()->like('file_target', $qb->createNamedParameter($this->dbConn->escapeLikeParameter($path) . '_%'))); + } else { + $qb->andWhere($qb->expr()->eq('file_target', $qb->createNamedParameter($path))); + } + + $cursor = $qb->executeQuery(); + + while ($data = $cursor->fetch()) { + if ($data['fileid'] && $data['path'] === null) { + $data['path'] = (string)$data['path']; + $data['name'] = (string)$data['name']; + $data['checksum'] = (string)$data['checksum']; + } + if ($this->isAccessibleResult($data)) { + $shares[] = $this->createShare($data); + } + } + $cursor->closeCursor(); + } elseif ($shareType === IShare::TYPE_GROUP) { + // get the parent share info (s) along with the child one (s2) + $qb = $this->dbConn->getQueryBuilder(); + $qb->select('s.*', 's2.permissions AS s2_permissions', 's2.accepted AS s2_accepted', 's2.file_target AS s2_file_target', 's2.parent AS s2_parent', + 'f.fileid', 'f.path', 'f.permissions AS f_permissions', 'f.storage', 'f.path_hash', + 'f.parent AS f_parent', 'f.name', 'f.mimetype', 'f.mimepart', 'f.size', 'f.mtime', 'f.storage_mtime', + 'f.encrypted', 'f.unencrypted_size', 'f.etag', 'f.checksum' + ) + ->selectAlias('st.id', 'storage_string_id') + ->from('share', 's2') + ->leftJoin('s2', 'filecache', 'f', $qb->expr()->eq('s2.file_source', 'f.fileid')) + ->leftJoin('f', 'storages', 'st', $qb->expr()->eq('f.storage', 'st.numeric_id')) + ->leftJoin('s2', 'share', 's', $qb->expr()->eq('s2.parent', 's.id')) + ->where($qb->expr()->eq('s2.share_with', $qb->createNamedParameter($userId))) + ->andWhere($qb->expr()->eq('s2.share_type', $qb->createNamedParameter(IShare::TYPE_USERGROUP))) + ->andWhere($qb->expr()->in('s2.item_type', $qb->createNamedParameter(['file', 'folder'], IQueryBuilder::PARAM_STR_ARRAY))) + ->orderBy('s2.id') + ->setFirstResult($offset); + if ($limit !== -1) { + $qb->setMaxResults($limit); + } + + if ($forChildren) { + $qb->andWhere($qb->expr()->like('s2.file_target', $qb->createNamedParameter($this->dbConn->escapeLikeParameter($path) . '_%'))); + } else { + $qb->andWhere($qb->expr()->eq('s2.file_target', $qb->createNamedParameter($path))); + } + + $cursor = $qb->executeQuery(); + while ($data = $cursor->fetch()) { + if ($this->isAccessibleResult($data)) { + $share = $this->createShare($data); + // patch the parent data with the user-specific changes + $share->setPermissions((int)$data['s2_permissions']); + $share->setStatus((int)$data['s2_accepted']); + $share->setTarget($data['s2_file_target']); + $share->setParent($data['s2_parent']); + $shares[] = $share; + } + } + $cursor->closeCursor(); + } else { + throw new BackendError('Invalid backend'); + } + + return $shares; + } + /** * Get a share by token * diff --git a/lib/private/Share20/Manager.php b/lib/private/Share20/Manager.php index c01f9fa5015..466401d1437 100644 --- a/lib/private/Share20/Manager.php +++ b/lib/private/Share20/Manager.php @@ -7,6 +7,7 @@ */ namespace OC\Share20; +use ArrayIterator; use OC\Core\AppInfo\ConfigLexicon; use OC\Files\Mount\MoveableMount; use OC\KnownUser\KnownUserService; @@ -49,6 +50,7 @@ use OCP\Share\Exceptions\GenericShareException; use OCP\Share\Exceptions\ShareNotFound; use OCP\Share\Exceptions\ShareTokenException; use OCP\Share\IManager; +use OCP\Share\IPartialShareProvider; use OCP\Share\IProviderFactory; use OCP\Share\IShare; use OCP\Share\IShareProvider; @@ -1283,6 +1285,43 @@ class Manager implements IManager { return $shares; } + /** + * @inheritDoc + */ + public function getSharedWithByPath(string $userId, int $shareType, string $path, bool $forChildren, int $limit = 50, int $offset = 0): iterable { + try { + $provider = $this->factory->getProviderForType($shareType); + } catch (ProviderException $e) { + return []; + } + + if (!$provider instanceof IPartialShareProvider) { + throw new \RuntimeException(\get_class($provider) . ' must implement IPartialShareProvider'); + } + + $shares = $provider->getSharedWithByPath($userId, + $shareType, + $path, + $forChildren, + $limit, + $offset + ); + + if (\is_array($shares)) { + $shares = new ArrayIterator($shares); + } + + return new \CallbackFilterIterator($shares, function (IShare $share) { + // remove all shares which are already expired + try { + $this->checkShare($share); + return true; + } catch (ShareNotFound $e) { + return false; + } + }); + } + #[Override] public function getDeletedSharedWith(string $userId, int $shareType, ?Node $node = null, int $limit = 50, int $offset = 0): array { $shares = $this->getSharedWith($userId, $shareType, $node, $limit, $offset); diff --git a/lib/public/Share/IManager.php b/lib/public/Share/IManager.php index 8b238abc13f..9abaff980f2 100644 --- a/lib/public/Share/IManager.php +++ b/lib/public/Share/IManager.php @@ -134,6 +134,18 @@ interface IManager { */ public function getSharedWith(string $userId, int $shareType, ?Node $node = null, int $limit = 50, int $offset = 0): array; + /** + * Get shares shared with a $user filtering by $path. + * + * @param IShare::TYPE_* $shareType + * @param bool $forChildren if true, results should only include children of $path + * @param int $limit The maximum number of shares returned, -1 for all + * + * @return iterable + * @since 33.0.0 + */ + public function getSharedWithByPath(string $userId, int $shareType, string $path, bool $forChildren, int $limit = 50, int $offset = 0): iterable; + /** * Get deleted shares shared with $user. * Filter by $node if provided diff --git a/lib/public/Share/IPartialShareProvider.php b/lib/public/Share/IPartialShareProvider.php new file mode 100644 index 00000000000..fe0b4218c9a --- /dev/null +++ b/lib/public/Share/IPartialShareProvider.php @@ -0,0 +1,31 @@ + + * @since 33.0.0 + */ + public function getSharedWithByPath( + string $userId, + int $shareType, + string $path, + bool $forChildren, + int $limit, + int $offset, + ): iterable; +}