Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
| 1 | <?php |
||
| 46 | class UserMountCache implements IUserMountCache { |
||
| 47 | /** |
||
| 48 | * @var IDBConnection |
||
| 49 | */ |
||
| 50 | private $connection; |
||
| 51 | |||
| 52 | /** |
||
| 53 | * @var IUserManager |
||
| 54 | */ |
||
| 55 | private $userManager; |
||
| 56 | |||
| 57 | /** |
||
| 58 | * Cached mount info. |
||
| 59 | * Map of $userId to ICachedMountInfo. |
||
| 60 | * |
||
| 61 | * @var ICache |
||
| 62 | **/ |
||
| 63 | private $mountsForUsers; |
||
| 64 | |||
| 65 | /** |
||
| 66 | * @var ILogger |
||
| 67 | */ |
||
| 68 | private $logger; |
||
| 69 | |||
| 70 | /** |
||
| 71 | * @var ICache |
||
| 72 | */ |
||
| 73 | private $cacheInfoCache; |
||
| 74 | |||
| 75 | /** |
||
| 76 | * UserMountCache constructor. |
||
| 77 | * |
||
| 78 | * @param IDBConnection $connection |
||
| 79 | * @param IUserManager $userManager |
||
| 80 | * @param ILogger $logger |
||
| 81 | */ |
||
| 82 | public function __construct(IDBConnection $connection, IUserManager $userManager, ILogger $logger) { |
||
| 83 | $this->connection = $connection; |
||
| 84 | $this->userManager = $userManager; |
||
| 85 | $this->logger = $logger; |
||
| 86 | $this->cacheInfoCache = new CappedMemoryCache(); |
||
| 87 | $this->mountsForUsers = new CappedMemoryCache(); |
||
| 88 | } |
||
| 89 | |||
| 90 | public function registerMounts(IUser $user, array $mounts) { |
||
| 91 | // filter out non-proper storages coming from unit tests |
||
| 92 | $mounts = array_filter($mounts, function (IMountPoint $mount) { |
||
| 93 | return $mount instanceof SharedMount || $mount->getStorage() && $mount->getStorage()->getCache(); |
||
| 94 | }); |
||
| 95 | /** @var ICachedMountInfo[] $newMounts */ |
||
| 96 | $newMounts = array_map(function (IMountPoint $mount) use ($user) { |
||
| 97 | // filter out any storages which aren't scanned yet since we aren't interested in files from those storages (yet) |
||
| 98 | if ($mount->getStorageRootId() === -1) { |
||
| 99 | return null; |
||
| 100 | } else { |
||
| 101 | return new LazyStorageMountInfo($user, $mount); |
||
| 102 | } |
||
| 103 | }, $mounts); |
||
| 104 | $newMounts = array_values(array_filter($newMounts)); |
||
| 105 | |||
| 106 | $cachedMounts = $this->getMountsForUser($user); |
||
| 107 | $mountDiff = function (ICachedMountInfo $mount1, ICachedMountInfo $mount2) { |
||
| 108 | // since we are only looking for mounts for a specific user comparing on root id is enough |
||
| 109 | return $mount1->getRootId() - $mount2->getRootId(); |
||
| 110 | }; |
||
| 111 | |||
| 112 | /** @var ICachedMountInfo[] $addedMounts */ |
||
| 113 | $addedMounts = array_udiff($newMounts, $cachedMounts, $mountDiff); |
||
| 114 | /** @var ICachedMountInfo[] $removedMounts */ |
||
| 115 | $removedMounts = array_udiff($cachedMounts, $newMounts, $mountDiff); |
||
| 116 | |||
| 117 | $changedMounts = $this->findChangedMounts($newMounts, $cachedMounts); |
||
| 118 | |||
| 119 | foreach ($addedMounts as $mount) { |
||
| 120 | $this->addToCache($mount); |
||
| 121 | $this->mountsForUsers[$user->getUID()][] = $mount; |
||
| 122 | } |
||
| 123 | foreach ($removedMounts as $mount) { |
||
| 124 | $this->removeFromCache($mount); |
||
| 125 | $index = array_search($mount, $this->mountsForUsers[$user->getUID()]); |
||
| 126 | unset($this->mountsForUsers[$user->getUID()][$index]); |
||
| 127 | } |
||
| 128 | foreach ($changedMounts as $mount) { |
||
| 129 | $this->updateCachedMount($mount); |
||
| 130 | } |
||
| 131 | } |
||
| 132 | |||
| 133 | /** |
||
| 134 | * @param ICachedMountInfo[] $newMounts |
||
| 135 | * @param ICachedMountInfo[] $cachedMounts |
||
| 136 | * @return ICachedMountInfo[] |
||
| 137 | */ |
||
| 138 | private function findChangedMounts(array $newMounts, array $cachedMounts) { |
||
| 139 | $changed = []; |
||
| 140 | foreach ($newMounts as $newMount) { |
||
| 141 | foreach ($cachedMounts as $cachedMount) { |
||
| 142 | if ( |
||
| 143 | $newMount->getRootId() === $cachedMount->getRootId() && |
||
| 144 | ($newMount->getMountPoint() !== $cachedMount->getMountPoint() || $newMount->getMountId() !== $cachedMount->getMountId()) |
||
| 145 | ) { |
||
| 146 | $changed[] = $newMount; |
||
| 147 | } |
||
| 148 | } |
||
| 149 | } |
||
| 150 | return $changed; |
||
| 151 | } |
||
| 152 | |||
| 153 | private function addToCache(ICachedMountInfo $mount) { |
||
| 154 | if ($mount->getStorageId() !== -1) { |
||
| 155 | $this->connection->insertIfNotExist('*PREFIX*mounts', [ |
||
| 156 | 'storage_id' => $mount->getStorageId(), |
||
| 157 | 'root_id' => $mount->getRootId(), |
||
| 158 | 'user_id' => $mount->getUser()->getUID(), |
||
| 159 | 'mount_point' => $mount->getMountPoint(), |
||
| 160 | 'mount_id' => $mount->getMountId() |
||
| 161 | ], ['root_id', 'user_id']); |
||
| 162 | } else { |
||
| 163 | // in some cases this is legitimate, like orphaned shares |
||
| 164 | $this->logger->debug('Could not get storage info for mount at ' . $mount->getMountPoint()); |
||
| 165 | } |
||
| 166 | } |
||
| 167 | |||
| 168 | private function updateCachedMount(ICachedMountInfo $mount) { |
||
| 169 | $builder = $this->connection->getQueryBuilder(); |
||
| 170 | |||
| 171 | $query = $builder->update('mounts') |
||
| 172 | ->set('mount_point', $builder->createNamedParameter($mount->getMountPoint())) |
||
| 173 | ->set('mount_id', $builder->createNamedParameter($mount->getMountId(), IQueryBuilder::PARAM_INT)) |
||
| 174 | ->where($builder->expr()->eq('user_id', $builder->createNamedParameter($mount->getUser()->getUID()))) |
||
| 175 | ->andWhere($builder->expr()->eq('root_id', $builder->createNamedParameter($mount->getRootId(), IQueryBuilder::PARAM_INT))); |
||
| 176 | |||
| 177 | $query->execute(); |
||
| 178 | } |
||
| 179 | |||
| 180 | View Code Duplication | private function removeFromCache(ICachedMountInfo $mount) { |
|
|
|
|||
| 181 | $builder = $this->connection->getQueryBuilder(); |
||
| 182 | |||
| 183 | $query = $builder->delete('mounts') |
||
| 184 | ->where($builder->expr()->eq('user_id', $builder->createNamedParameter($mount->getUser()->getUID()))) |
||
| 185 | ->andWhere($builder->expr()->eq('root_id', $builder->createNamedParameter($mount->getRootId(), IQueryBuilder::PARAM_INT))); |
||
| 186 | $query->execute(); |
||
| 187 | } |
||
| 188 | |||
| 189 | private function dbRowToMountInfo(array $row) { |
||
| 190 | $user = $this->userManager->get($row['user_id']); |
||
| 191 | return new CachedMountInfo($user, (int)$row['storage_id'], (int)$row['root_id'], $row['mount_point'], $row['mount_id'], isset($row['path'])? $row['path']:''); |
||
| 192 | } |
||
| 193 | |||
| 194 | /** |
||
| 195 | * @param IUser $user |
||
| 196 | * @return ICachedMountInfo[] |
||
| 197 | */ |
||
| 198 | public function getMountsForUser(IUser $user) { |
||
| 199 | if (!isset($this->mountsForUsers[$user->getUID()])) { |
||
| 200 | $builder = $this->connection->getQueryBuilder(); |
||
| 201 | $query = $builder->select('storage_id', 'root_id', 'user_id', 'mount_point', 'mount_id', 'f.path') |
||
| 202 | ->from('mounts', 'm') |
||
| 203 | ->innerJoin('m', 'filecache', 'f', $builder->expr()->eq('m.root_id', 'f.fileid')) |
||
| 204 | ->where($builder->expr()->eq('user_id', $builder->createPositionalParameter($user->getUID()))); |
||
| 205 | |||
| 206 | $rows = $query->execute()->fetchAll(); |
||
| 207 | |||
| 208 | $this->mountsForUsers[$user->getUID()] = array_map([$this, 'dbRowToMountInfo'], $rows); |
||
| 209 | } |
||
| 210 | return $this->mountsForUsers[$user->getUID()]; |
||
| 211 | } |
||
| 212 | |||
| 213 | /** |
||
| 214 | * @param int $numericStorageId |
||
| 215 | * @return CachedMountInfo[] |
||
| 216 | */ |
||
| 217 | View Code Duplication | public function getMountsForStorageId($numericStorageId) { |
|
| 218 | $builder = $this->connection->getQueryBuilder(); |
||
| 219 | $query = $builder->select('storage_id', 'root_id', 'user_id', 'mount_point', 'mount_id', 'f.path') |
||
| 220 | ->from('mounts', 'm') |
||
| 221 | ->innerJoin('m', 'filecache', 'f' , $builder->expr()->eq('m.root_id', 'f.fileid')) |
||
| 222 | ->where($builder->expr()->eq('storage_id', $builder->createPositionalParameter($numericStorageId, IQueryBuilder::PARAM_INT))); |
||
| 223 | |||
| 224 | $rows = $query->execute()->fetchAll(); |
||
| 225 | |||
| 226 | return array_map([$this, 'dbRowToMountInfo'], $rows); |
||
| 227 | } |
||
| 228 | |||
| 229 | /** |
||
| 230 | * @param int $rootFileId |
||
| 231 | * @return CachedMountInfo[] |
||
| 232 | */ |
||
| 233 | View Code Duplication | public function getMountsForRootId($rootFileId) { |
|
| 234 | $builder = $this->connection->getQueryBuilder(); |
||
| 235 | $query = $builder->select('storage_id', 'root_id', 'user_id', 'mount_point', 'mount_id', 'f.path') |
||
| 236 | ->from('mounts', 'm') |
||
| 237 | ->innerJoin('m', 'filecache', 'f', $builder->expr()->eq('m.root_id', 'f.fileid')) |
||
| 238 | ->where($builder->expr()->eq('root_id', $builder->createPositionalParameter($rootFileId, IQueryBuilder::PARAM_INT))); |
||
| 239 | |||
| 240 | $rows = $query->execute()->fetchAll(); |
||
| 241 | |||
| 242 | return array_map([$this, 'dbRowToMountInfo'], $rows); |
||
| 243 | } |
||
| 244 | |||
| 245 | /** |
||
| 246 | * @param $fileId |
||
| 247 | * @return array |
||
| 248 | * @throws \OCP\Files\NotFoundException |
||
| 249 | */ |
||
| 250 | private function getCacheInfoFromFileId($fileId) { |
||
| 251 | if (!isset($this->cacheInfoCache[$fileId])) { |
||
| 252 | $builder = $this->connection->getQueryBuilder(); |
||
| 253 | $query = $builder->select('storage', 'path', 'mimetype') |
||
| 254 | ->from('filecache') |
||
| 255 | ->where($builder->expr()->eq('fileid', $builder->createNamedParameter($fileId, IQueryBuilder::PARAM_INT))); |
||
| 256 | |||
| 257 | $row = $query->execute()->fetch(); |
||
| 258 | if (is_array($row)) { |
||
| 259 | $this->cacheInfoCache[$fileId] = [ |
||
| 260 | (int)$row['storage'], |
||
| 261 | $row['path'], |
||
| 262 | (int)$row['mimetype'] |
||
| 263 | ]; |
||
| 264 | } else { |
||
| 265 | throw new NotFoundException('File with id "' . $fileId . '" not found'); |
||
| 266 | } |
||
| 267 | } |
||
| 268 | return $this->cacheInfoCache[$fileId]; |
||
| 269 | } |
||
| 270 | |||
| 271 | /** |
||
| 272 | * @param int $fileId |
||
| 273 | * @return ICachedMountInfo[] |
||
| 274 | * @since 9.0.0 |
||
| 275 | */ |
||
| 276 | public function getMountsForFileId($fileId) { |
||
| 277 | try { |
||
| 278 | list($storageId, $internalPath) = $this->getCacheInfoFromFileId($fileId); |
||
| 279 | } catch (NotFoundException $e) { |
||
| 280 | return []; |
||
| 281 | } |
||
| 282 | $mountsForStorage = $this->getMountsForStorageId($storageId); |
||
| 283 | |||
| 284 | // filter mounts that are from the same storage but a different directory |
||
| 285 | return array_filter($mountsForStorage, function (ICachedMountInfo $mount) use ($internalPath, $fileId) { |
||
| 286 | if ($fileId === $mount->getRootId()) { |
||
| 287 | return true; |
||
| 288 | } |
||
| 289 | $internalMountPath = $mount->getRootInternalPath(); |
||
| 290 | |||
| 291 | return $internalMountPath === '' || substr($internalPath, 0, strlen($internalMountPath) + 1) === $internalMountPath . '/'; |
||
| 292 | }); |
||
| 293 | } |
||
| 294 | |||
| 295 | /** |
||
| 296 | * Remove all cached mounts for a user |
||
| 297 | * |
||
| 298 | * @param IUser $user |
||
| 299 | */ |
||
| 300 | View Code Duplication | public function removeUserMounts(IUser $user) { |
|
| 307 | |||
| 308 | public function removeUserStorageMount($storageId, $userId) { |
||
| 316 | |||
| 317 | View Code Duplication | public function remoteStorageMounts($storageId) { |
|
| 324 | } |
||
| 325 |
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.