| Total Complexity | 44 |
| Total Lines | 223 |
| Duplicated Lines | 0 % |
| Changes | 0 | ||
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 |
||
| 46 | class Notify extends Base { |
||
| 47 | /** @var GlobalStoragesService */ |
||
| 48 | private $globalService; |
||
| 49 | /** @var IDBConnection */ |
||
| 50 | private $connection; |
||
| 51 | /** @var \OCP\DB\QueryBuilder\IQueryBuilder */ |
||
| 52 | private $updateQuery; |
||
| 53 | /** @var ILogger */ |
||
| 54 | private $logger; |
||
| 55 | |||
| 56 | function __construct(GlobalStoragesService $globalService, IDBConnection $connection, ILogger $logger) { |
||
| 57 | parent::__construct(); |
||
| 58 | $this->globalService = $globalService; |
||
| 59 | $this->connection = $connection; |
||
| 60 | $this->logger = $logger; |
||
| 61 | $this->updateQuery = $this->getUpdateQuery($this->connection); |
||
|
|
|||
| 62 | } |
||
| 63 | |||
| 64 | protected function configure() { |
||
| 65 | $this |
||
| 66 | ->setName('files_external:notify') |
||
| 67 | ->setDescription('Listen for active update notifications for a configured external mount') |
||
| 68 | ->addArgument( |
||
| 69 | 'mount_id', |
||
| 70 | InputArgument::REQUIRED, |
||
| 71 | 'the mount id of the mount to listen to' |
||
| 72 | )->addOption( |
||
| 73 | 'user', |
||
| 74 | 'u', |
||
| 75 | InputOption::VALUE_REQUIRED, |
||
| 76 | 'The username for the remote mount (required only for some mount configuration that don\'t store credentials)' |
||
| 77 | )->addOption( |
||
| 78 | 'password', |
||
| 79 | 'p', |
||
| 80 | InputOption::VALUE_REQUIRED, |
||
| 81 | 'The password for the remote mount (required only for some mount configuration that don\'t store credentials)' |
||
| 82 | )->addOption( |
||
| 83 | 'path', |
||
| 84 | '', |
||
| 85 | InputOption::VALUE_REQUIRED, |
||
| 86 | 'The directory in the storage to listen for updates in', |
||
| 87 | '/' |
||
| 88 | ); |
||
| 89 | parent::configure(); |
||
| 90 | } |
||
| 91 | |||
| 92 | protected function execute(InputInterface $input, OutputInterface $output) { |
||
| 93 | $mount = $this->globalService->getStorage($input->getArgument('mount_id')); |
||
| 94 | if (is_null($mount)) { |
||
| 95 | $output->writeln('<error>Mount not found</error>'); |
||
| 96 | return 1; |
||
| 97 | } |
||
| 98 | $noAuth = false; |
||
| 99 | try { |
||
| 100 | $authBackend = $mount->getAuthMechanism(); |
||
| 101 | $authBackend->manipulateStorageConfig($mount); |
||
| 102 | } catch (InsufficientDataForMeaningfulAnswerException $e) { |
||
| 103 | $noAuth = true; |
||
| 104 | } catch (StorageNotAvailableException $e) { |
||
| 105 | $noAuth = true; |
||
| 106 | } |
||
| 107 | |||
| 108 | if ($input->getOption('user')) { |
||
| 109 | $mount->setBackendOption('user', $input->getOption('user')); |
||
| 110 | } else if (isset($_ENV['NOTIFY_USER'])) { |
||
| 111 | $mount->setBackendOption('user', $_ENV['NOTIFY_USER']); |
||
| 112 | } else if (isset($_SERVER['NOTIFY_USER'])) { |
||
| 113 | $mount->setBackendOption('user', $_SERVER['NOTIFY_USER']); |
||
| 114 | } |
||
| 115 | if ($input->getOption('password')) { |
||
| 116 | $mount->setBackendOption('password', $input->getOption('password')); |
||
| 117 | } else if (isset($_ENV['NOTIFY_PASSWORD'])) { |
||
| 118 | $mount->setBackendOption('password', $_ENV['NOTIFY_PASSWORD']); |
||
| 119 | } else if (isset($_SERVER['NOTIFY_PASSWORD'])) { |
||
| 120 | $mount->setBackendOption('password', $_SERVER['NOTIFY_PASSWORD']); |
||
| 121 | } |
||
| 122 | |||
| 123 | try { |
||
| 124 | $storage = $this->createStorage($mount); |
||
| 125 | } catch (\Exception $e) { |
||
| 126 | $output->writeln('<error>Error while trying to create storage</error>'); |
||
| 127 | if ($noAuth) { |
||
| 128 | $output->writeln('<error>Username and/or password required</error>'); |
||
| 129 | } |
||
| 130 | return 1; |
||
| 131 | } |
||
| 132 | if (!$storage instanceof INotifyStorage) { |
||
| 133 | $output->writeln('<error>Mount of type "' . $mount->getBackend()->getText() . '" does not support active update notifications</error>'); |
||
| 134 | return 1; |
||
| 135 | } |
||
| 136 | |||
| 137 | $verbose = $input->getOption('verbose'); |
||
| 138 | |||
| 139 | $path = trim($input->getOption('path'), '/'); |
||
| 140 | $notifyHandler = $storage->notify($path); |
||
| 141 | $this->selfTest($storage, $notifyHandler, $verbose, $output); |
||
| 142 | $notifyHandler->listen(function (IChange $change) use ($mount, $verbose, $output) { |
||
| 143 | if ($verbose) { |
||
| 144 | $this->logUpdate($change, $output); |
||
| 145 | } |
||
| 146 | if ($change instanceof IRenameChange) { |
||
| 147 | $this->markParentAsOutdated($mount->getId(), $change->getTargetPath(), $output); |
||
| 148 | } |
||
| 149 | $this->markParentAsOutdated($mount->getId(), $change->getPath(), $output); |
||
| 150 | }); |
||
| 151 | } |
||
| 152 | |||
| 153 | private function createStorage(StorageConfig $mount) { |
||
| 154 | $class = $mount->getBackend()->getStorageClass(); |
||
| 155 | return new $class($mount->getBackendOptions()); |
||
| 156 | } |
||
| 157 | |||
| 158 | private function markParentAsOutdated($mountId, $path, OutputInterface $output) { |
||
| 159 | $parent = ltrim(dirname($path), '/'); |
||
| 160 | if ($parent === '.') { |
||
| 161 | $parent = ''; |
||
| 162 | } |
||
| 163 | |||
| 164 | try { |
||
| 165 | $this->updateQuery->execute([$parent, $mountId]); |
||
| 166 | } catch (DriverException $ex) { |
||
| 167 | $this->logger->logException($ex, ['app' => 'files_external', 'message' => 'Error while trying to mark folder as outdated', 'level' => ILogger::WARN]); |
||
| 168 | $this->connection = $this->reconnectToDatabase($this->connection, $output); |
||
| 169 | $output->writeln('<info>Needed to reconnect to the database</info>'); |
||
| 170 | $this->updateQuery = $this->getUpdateQuery($this->connection); |
||
| 171 | $this->updateQuery->execute([$parent, $mountId]); |
||
| 172 | } |
||
| 173 | } |
||
| 174 | |||
| 175 | private function logUpdate(IChange $change, OutputInterface $output) { |
||
| 199 | } |
||
| 200 | |||
| 201 | /** |
||
| 202 | * @return \Doctrine\DBAL\Statement |
||
| 203 | */ |
||
| 204 | private function getUpdateQuery(IDBConnection $connection) { |
||
| 208 | WHERE `path` = ? |
||
| 209 | AND `storage` IN (SELECT storage_id FROM *PREFIX*mounts WHERE mount_id = ?)' |
||
| 210 | ); |
||
| 211 | } |
||
| 212 | |||
| 213 | /** |
||
| 214 | * @return \OCP\IDBConnection |
||
| 215 | */ |
||
| 216 | private function reconnectToDatabase(IDBConnection $connection, OutputInterface $output) { |
||
| 217 | try { |
||
| 218 | $connection->close(); |
||
| 219 | } catch (\Exception $ex) { |
||
| 220 | $this->logger->logException($ex, ['app' => 'files_external', 'message' => 'Error while disconnecting from DB', 'level' => ILogger::WARN]); |
||
| 221 | $output->writeln("<info>Error while disconnecting from database: {$ex->getMessage()}</info>"); |
||
| 222 | } |
||
| 223 | while (!$connection->isConnected()) { |
||
| 224 | try { |
||
| 225 | $connection->connect(); |
||
| 226 | } catch (\Exception $ex) { |
||
| 227 | $this->logger->logException($ex, ['app' => 'files_external', 'message' => 'Error while re-connecting to database', 'level' => ILogger::WARN]); |
||
| 228 | $output->writeln("<info>Error while re-connecting to database: {$ex->getMessage()}</info>"); |
||
| 229 | sleep(60); |
||
| 230 | } |
||
| 231 | } |
||
| 232 | return $connection; |
||
| 233 | } |
||
| 234 | |||
| 235 | |||
| 236 | private function selfTest(IStorage $storage, INotifyHandler $notifyHandler, $verbose, OutputInterface $output) { |
||
| 269 | } |
||
| 270 | } |
||
| 271 | } |
||
| 272 |
Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.
Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..