countProblemShares(); if ($count === 0) { return; } $output->startProgress($count); $lastUser = ''; $userMounts = []; $userFolder = null; foreach ($this->getProblemShares() as $shareInfo) { if ($shareInfo['share_type'] === IShare::TYPE_CIRCLE) { $recipient = $this->getCircleShareOwner($shareInfo['share_with']); if ($recipient === null) { continue; } } else { $recipient = $this->userManager->getExistingUser($shareInfo['share_with']); } if (!$recipient->isEnabled()) { continue; } // since we ordered the share by user, we can reuse the last data until we get to the next user if ($lastUser !== $recipient->getUID()) { $lastUser = $recipient->getUID(); $this->setupManager->tearDown(); $this->setupManager->setupForUser($recipient); $mounts = $this->userMountCache->getMountsForUser($recipient); $mountPoints = array_map(fn (ICachedMountInfo $mount) => $mount->getMountPoint(), $mounts); $userMounts = array_combine($mountPoints, $mounts); $userFolder = $this->rootFolder->getUserFolder($recipient->getUID()); } $oldTarget = $shareInfo['file_target']; $newTarget = $this->cleanTarget($oldTarget); $absoluteNewTarget = $userFolder->getFullPath($newTarget); try { $targetParentNode = $this->rootFolder->get(dirname($absoluteNewTarget)); } catch (NotFoundException) { $absoluteNewTarget = $userFolder->getFullPath(basename($newTarget)); $targetParentNode = $userFolder; } try { $absoluteNewTarget = $this->shareTargetValidator->generateUniqueTarget( (int)$shareInfo['file_source'], $absoluteNewTarget, $targetParentNode->getMountPoint(), $userMounts, ); $newTarget = $userFolder->getRelativePath($absoluteNewTarget); $this->moveShare((string)$shareInfo['id'], $newTarget); $oldMountPoint = "/{$recipient->getUID()}/files$oldTarget/"; $newMountPoint = "/{$recipient->getUID()}/files$newTarget/"; $userMounts[$newMountPoint] = $userMounts[$oldMountPoint]; unset($userMounts[$oldMountPoint]); } catch (\Exception $e) { $msg = 'error cleaning up share target: ' . $e->getMessage(); $this->logger->error($msg, ['exception' => $e, 'app' => 'files_sharing']); $output->warning($msg); } $output->advance(); } $this->cacheFactory->createDistributed('circles/')->clear(); $output->finishProgress(); $output->info("Fixed $count shares"); } private function countProblemShares(): int { $query = $this->connection->getQueryBuilder(); $query->select($query->func()->count('id')) ->from('share') ->where($query->expr()->like('file_target', $query->createNamedParameter('% (_) (_)%'))) ->andWhere($query->expr()->in('share_type', $query->createNamedParameter(self::USER_SHARE_TYPES, IQueryBuilder::PARAM_INT_ARRAY), IQueryBuilder::PARAM_INT_ARRAY)); return (int)$query->executeQuery()->fetchOne(); } private function moveShare(string $id, string $target): void { // since we only process user-specific shares, we can just move them // without having to check if we need to create a user-specific override $query = $this->connection->getQueryBuilder(); $query->update('share') ->set('file_target', $query->createNamedParameter($target)) ->where($query->expr()->eq('id', $query->createNamedParameter($id))) ->executeStatement(); } /** * @return \Traversable */ private function getProblemShares(): \Traversable { $query = $this->connection->getQueryBuilder(); $query->select('id', 'share_type', 'share_with', 'file_source', 'file_target') ->from('share') ->where($query->expr()->like('file_target', $query->createNamedParameter('% (_) (_)%'))) ->andWhere($query->expr()->orX( $query->expr()->in('share_type', $query->createNamedParameter(self::USER_SHARE_TYPES, IQueryBuilder::PARAM_INT_ARRAY), IQueryBuilder::PARAM_INT_ARRAY), $query->expr()->andX( $query->expr()->eq('share_type', $query->createNamedParameter( IShare::TYPE_CIRCLE, IQueryBuilder::PARAM_INT), IQueryBuilder::PARAM_INT), $query->expr()->isNotNull('parent'), ), )) ->orderBy('share_with') ->addOrderBy('id'); $result = $query->executeQuery(); /** @var \Traversable $rows */ $rows = $result->iterateAssociative(); return $rows; } private function cleanTarget(string $target): string { return preg_replace('/( \([2-9]\)){2,}/', '', $target); } /** * Get the user of a federated user from circles, if the circle id belong to a user */ private function getCircleShareOwner(string $circleId): ?IUser { $query = $this->connection->getQueryBuilder(); $query->select('name') ->from('circles_circle') ->where($query->expr()->eq('unique_id', $query->createNamedParameter($circleId))) ->andWhere($query->expr()->eq('source', $query->createNamedParameter(1, IQueryBuilder::PARAM_INT))); $name = $query->executeQuery()->fetchOne(); if ($name === false) { return null; } [$type, $userId,] = explode(':', $name, 3); if ($type === 'user') { return $this->userManager->getExistingUser($userId); } else { return null; } } }