Total Complexity | 55 |
Total Lines | 318 |
Duplicated Lines | 0 % |
Changes | 1 | ||
Bugs | 0 | Features | 1 |
Complex classes like Notify often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use Notify, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
50 | class Notify extends Base { |
||
51 | /** @var GlobalStoragesService */ |
||
52 | private $globalService; |
||
53 | /** @var IDBConnection */ |
||
54 | private $connection; |
||
55 | /** @var ILogger */ |
||
56 | private $logger; |
||
57 | /** @var IUserManager */ |
||
58 | private $userManager; |
||
59 | |||
60 | public function __construct( |
||
71 | } |
||
72 | |||
73 | protected function configure() { |
||
74 | $this |
||
75 | ->setName('files_external:notify') |
||
76 | ->setDescription('Listen for active update notifications for a configured external mount') |
||
77 | ->addArgument( |
||
78 | 'mount_id', |
||
79 | InputArgument::REQUIRED, |
||
80 | 'the mount id of the mount to listen to' |
||
81 | )->addOption( |
||
82 | 'user', |
||
83 | 'u', |
||
84 | InputOption::VALUE_REQUIRED, |
||
85 | 'The username for the remote mount (required only for some mount configuration that don\'t store credentials)' |
||
86 | )->addOption( |
||
87 | 'password', |
||
88 | 'p', |
||
89 | InputOption::VALUE_REQUIRED, |
||
90 | 'The password for the remote mount (required only for some mount configuration that don\'t store credentials)' |
||
91 | )->addOption( |
||
92 | 'path', |
||
93 | '', |
||
94 | InputOption::VALUE_REQUIRED, |
||
95 | 'The directory in the storage to listen for updates in', |
||
96 | '/' |
||
97 | )->addOption( |
||
98 | 'no-self-check', |
||
99 | '', |
||
100 | InputOption::VALUE_NONE, |
||
101 | 'Disable self check on startup' |
||
102 | )->addOption( |
||
103 | 'dry-run', |
||
104 | '', |
||
105 | InputOption::VALUE_NONE, |
||
106 | 'Don\'t make any changes, only log detected changes' |
||
107 | ); |
||
108 | parent::configure(); |
||
109 | } |
||
110 | |||
111 | private function getUserOption(InputInterface $input): ?string { |
||
112 | if ($input->getOption('user')) { |
||
113 | return (string)$input->getOption('user'); |
||
114 | } elseif (isset($_ENV['NOTIFY_USER'])) { |
||
115 | return (string)$_ENV['NOTIFY_USER']; |
||
116 | } elseif (isset($_SERVER['NOTIFY_USER'])) { |
||
117 | return (string)$_SERVER['NOTIFY_USER']; |
||
118 | } else { |
||
119 | return null; |
||
120 | } |
||
121 | } |
||
122 | |||
123 | private function getPasswordOption(InputInterface $input): ?string { |
||
132 | } |
||
133 | } |
||
134 | |||
135 | protected function execute(InputInterface $input, OutputInterface $output): int { |
||
136 | $mount = $this->globalService->getStorage($input->getArgument('mount_id')); |
||
137 | if (is_null($mount)) { |
||
138 | $output->writeln('<error>Mount not found</error>'); |
||
139 | return 1; |
||
140 | } |
||
141 | $noAuth = false; |
||
142 | |||
143 | $userOption = $this->getUserOption($input); |
||
144 | $passwordOption = $this->getPasswordOption($input); |
||
145 | |||
146 | // if only the user is provided, we get the user object to pass along to the auth backend |
||
147 | // this allows using saved user credentials |
||
148 | $user = ($userOption && !$passwordOption) ? $this->userManager->get($userOption) : null; |
||
149 | |||
150 | try { |
||
151 | $authBackend = $mount->getAuthMechanism(); |
||
152 | $authBackend->manipulateStorageConfig($mount, $user); |
||
153 | } catch (InsufficientDataForMeaningfulAnswerException $e) { |
||
154 | $noAuth = true; |
||
155 | } catch (StorageNotAvailableException $e) { |
||
156 | $noAuth = true; |
||
157 | } |
||
158 | |||
159 | if ($userOption) { |
||
160 | $mount->setBackendOption('user', $userOption); |
||
161 | } |
||
162 | if ($passwordOption) { |
||
163 | $mount->setBackendOption('password', $passwordOption); |
||
164 | } |
||
165 | |||
166 | try { |
||
167 | $backend = $mount->getBackend(); |
||
168 | $backend->manipulateStorageConfig($mount, $user); |
||
169 | } catch (InsufficientDataForMeaningfulAnswerException $e) { |
||
170 | $noAuth = true; |
||
171 | } catch (StorageNotAvailableException $e) { |
||
172 | $noAuth = true; |
||
173 | } |
||
174 | |||
175 | try { |
||
176 | $storage = $this->createStorage($mount); |
||
177 | } catch (\Exception $e) { |
||
178 | $output->writeln('<error>Error while trying to create storage</error>'); |
||
179 | if ($noAuth) { |
||
180 | $output->writeln('<error>Username and/or password required</error>'); |
||
181 | } |
||
182 | return 1; |
||
183 | } |
||
184 | if (!$storage instanceof INotifyStorage) { |
||
185 | $output->writeln('<error>Mount of type "' . $mount->getBackend()->getText() . '" does not support active update notifications</error>'); |
||
186 | return 1; |
||
187 | } |
||
188 | |||
189 | $dryRun = $input->getOption('dry-run'); |
||
190 | if ($dryRun && $output->getVerbosity() < OutputInterface::VERBOSITY_VERBOSE) { |
||
191 | $output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE); |
||
192 | } |
||
193 | |||
194 | $path = trim($input->getOption('path'), '/'); |
||
195 | $notifyHandler = $storage->notify($path); |
||
196 | |||
197 | if (!$input->getOption('no-self-check')) { |
||
198 | $this->selfTest($storage, $notifyHandler, $output); |
||
199 | } |
||
200 | |||
201 | $notifyHandler->listen(function (IChange $change) use ($mount, $output, $dryRun) { |
||
202 | $this->logUpdate($change, $output); |
||
203 | if ($change instanceof IRenameChange) { |
||
204 | $this->markParentAsOutdated($mount->getId(), $change->getTargetPath(), $output, $dryRun); |
||
205 | } |
||
206 | $this->markParentAsOutdated($mount->getId(), $change->getPath(), $output, $dryRun); |
||
207 | }); |
||
208 | return 0; |
||
209 | } |
||
210 | |||
211 | private function createStorage(StorageConfig $mount) { |
||
212 | $class = $mount->getBackend()->getStorageClass(); |
||
213 | return new $class($mount->getBackendOptions()); |
||
214 | } |
||
215 | |||
216 | private function markParentAsOutdated($mountId, $path, OutputInterface $output, bool $dryRun) { |
||
217 | $parent = ltrim(dirname($path), '/'); |
||
218 | if ($parent === '.') { |
||
219 | $parent = ''; |
||
220 | } |
||
221 | |||
222 | try { |
||
223 | $storages = $this->getStorageIds($mountId, $parent); |
||
224 | } catch (DriverException $ex) { |
||
225 | $this->logger->logException($ex, ['message' => 'Error while trying to find correct storage ids.', 'level' => ILogger::WARN]); |
||
|
|||
226 | $this->connection = $this->reconnectToDatabase($this->connection, $output); |
||
227 | $output->writeln('<info>Needed to reconnect to the database</info>'); |
||
228 | $storages = $this->getStorageIds($mountId, $path); |
||
229 | } |
||
230 | if (count($storages) === 0) { |
||
231 | $output->writeln(" no users found with access to '$parent', skipping", OutputInterface::VERBOSITY_VERBOSE); |
||
232 | return; |
||
233 | } |
||
234 | |||
235 | $users = array_map(function (array $storage) { |
||
236 | return $storage['user_id']; |
||
237 | }, $storages); |
||
238 | |||
239 | $output->writeln(" marking '$parent' as outdated for " . implode(', ', $users), OutputInterface::VERBOSITY_VERBOSE); |
||
240 | |||
241 | $storageIds = array_map(function (array $storage) { |
||
242 | return intval($storage['storage_id']); |
||
243 | }, $storages); |
||
244 | $storageIds = array_values(array_unique($storageIds)); |
||
245 | |||
246 | if ($dryRun) { |
||
247 | $output->writeln(" dry-run: skipping database write"); |
||
248 | } else { |
||
249 | $result = $this->updateParent($storageIds, $parent); |
||
250 | if ($result === 0) { |
||
251 | //TODO: Find existing parent further up the tree in the database and register that folder instead. |
||
252 | $this->logger->info('Failed updating parent for "' . $path . '" while trying to register change. It may not exist in the filecache.'); |
||
253 | } |
||
254 | } |
||
255 | } |
||
256 | |||
257 | private function logUpdate(IChange $change, OutputInterface $output) { |
||
281 | } |
||
282 | |||
283 | private function getStorageIds(int $mountId, string $path): array { |
||
294 | } |
||
295 | |||
296 | /** |
||
297 | * @param array $storageIds |
||
298 | * @param string $parent |
||
299 | * @return int |
||
300 | */ |
||
301 | private function updateParent($storageIds, $parent) { |
||
302 | $pathHash = md5(trim(\OC_Util::normalizeUnicode($parent), '/')); |
||
303 | $qb = $this->connection->getQueryBuilder(); |
||
304 | return $qb |
||
305 | ->update('filecache') |
||
306 | ->set('size', $qb->createNamedParameter(-1, IQueryBuilder::PARAM_INT)) |
||
307 | ->where($qb->expr()->in('storage', $qb->createNamedParameter($storageIds, IQueryBuilder::PARAM_INT_ARRAY, ':storage_ids'))) |
||
308 | ->andWhere($qb->expr()->eq('path_hash', $qb->createNamedParameter($pathHash, IQueryBuilder::PARAM_STR))) |
||
309 | ->execute(); |
||
310 | } |
||
311 | |||
312 | /** |
||
313 | * @return \OCP\IDBConnection |
||
314 | */ |
||
315 | private function reconnectToDatabase(IDBConnection $connection, OutputInterface $output) { |
||
316 | try { |
||
317 | $connection->close(); |
||
318 | } catch (\Exception $ex) { |
||
319 | $this->logger->logException($ex, ['app' => 'files_external', 'message' => 'Error while disconnecting from DB', 'level' => ILogger::WARN]); |
||
320 | $output->writeln("<info>Error while disconnecting from database: {$ex->getMessage()}</info>"); |
||
321 | } |
||
322 | while (!$connection->isConnected()) { |
||
323 | try { |
||
324 | $connection->connect(); |
||
325 | } catch (\Exception $ex) { |
||
326 | $this->logger->logException($ex, ['app' => 'files_external', 'message' => 'Error while re-connecting to database', 'level' => ILogger::WARN]); |
||
327 | $output->writeln("<info>Error while re-connecting to database: {$ex->getMessage()}</info>"); |
||
328 | sleep(60); |
||
329 | } |
||
330 | } |
||
331 | return $connection; |
||
332 | } |
||
333 | |||
334 | |||
335 | private function selfTest(IStorage $storage, INotifyHandler $notifyHandler, OutputInterface $output) { |
||
368 | } |
||
369 | } |
||
370 | } |
||
371 |
This class constant has been deprecated. The supplier of the class has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.