diff --git a/Neos.Flow/Classes/Monitor/ChangeDetectionStrategy/ModificationTimeStrategy.php b/Neos.Flow/Classes/Monitor/ChangeDetectionStrategy/ModificationTimeStrategy.php index 39a239c735..b4c161ac45 100644 --- a/Neos.Flow/Classes/Monitor/ChangeDetectionStrategy/ModificationTimeStrategy.php +++ b/Neos.Flow/Classes/Monitor/ChangeDetectionStrategy/ModificationTimeStrategy.php @@ -18,7 +18,7 @@ /** * A change detection strategy based on modification times */ -class ModificationTimeStrategy implements ChangeDetectionStrategyInterface, StrategyWithMarkDeletedInterface +class ModificationTimeStrategy implements ChangeDetectionStrategyInterface, StrategyWithMarkDeletedInterface, StrategyWithFlushDeletedOnPathInterface { /** * @var \Neos\Flow\Monitor\FileMonitor @@ -64,6 +64,32 @@ public function setFileMonitor(FileMonitor $fileMonitor) $this->filesAndModificationTimes = json_decode($this->cache->get($this->fileMonitor->getIdentifier() . '_filesAndModificationTimes'), true); } + /** + * @param string $onPath + * @param array $filesIgnoreMask files to ignore as we are sure they exist + * @return array + */ + public function flushDeletedOnPath(string $onPath, array $filesIgnoreMask): array + { + $deletedFiles = []; + foreach ($this->filesAndModificationTimes as $pathAndFilename => $modificationTime) { + if (!str_starts_with($pathAndFilename, $onPath)) { + continue; + } + if (isset($filesIgnoreMask[$pathAndFilename])) { + continue; + } + if (file_exists($pathAndFilename)) { + // should not happen? + continue; + } + $this->modificationTimesChanged = true; + unset($this->filesAndModificationTimes[$pathAndFilename]); + $deletedFiles[$pathAndFilename] = ChangeDetectionStrategyInterface::STATUS_DELETED; + } + return $deletedFiles; + } + /** * Checks if the specified file has changed * diff --git a/Neos.Flow/Classes/Monitor/ChangeDetectionStrategy/StrategyWithFlushDeletedOnPathInterface.php b/Neos.Flow/Classes/Monitor/ChangeDetectionStrategy/StrategyWithFlushDeletedOnPathInterface.php new file mode 100644 index 0000000000..a1383130f3 --- /dev/null +++ b/Neos.Flow/Classes/Monitor/ChangeDetectionStrategy/StrategyWithFlushDeletedOnPathInterface.php @@ -0,0 +1,27 @@ + $filesIgnoreMask files to ignore as we are sure they exist + * @return array + */ + public function flushDeletedOnPath(string $onPath, array $filesIgnoreMask): array; +} diff --git a/Neos.Flow/Classes/Monitor/ChangeDetectionStrategy/StrategyWithMarkDeletedInterface.php b/Neos.Flow/Classes/Monitor/ChangeDetectionStrategy/StrategyWithMarkDeletedInterface.php index 30017d49b8..0538614cef 100644 --- a/Neos.Flow/Classes/Monitor/ChangeDetectionStrategy/StrategyWithMarkDeletedInterface.php +++ b/Neos.Flow/Classes/Monitor/ChangeDetectionStrategy/StrategyWithMarkDeletedInterface.php @@ -14,6 +14,7 @@ /** * Contract for a change detection strategy that allows the FileMonitor to mark a file deleted directly. * + * @deprecated in favour of more reliable {@see StrategyWithFlushDeletedOnPathInterface} * @api */ interface StrategyWithMarkDeletedInterface diff --git a/Neos.Flow/Classes/Monitor/FileMonitor.php b/Neos.Flow/Classes/Monitor/FileMonitor.php index d2b5be6ffe..1dd723e3c1 100644 --- a/Neos.Flow/Classes/Monitor/FileMonitor.php +++ b/Neos.Flow/Classes/Monitor/FileMonitor.php @@ -17,6 +17,7 @@ use Neos\Flow\Log\PsrLoggerFactoryInterface; use Neos\Flow\Log\Utility\LogEnvironment; use Neos\Flow\Monitor\ChangeDetectionStrategy\ChangeDetectionStrategyInterface; +use Neos\Flow\Monitor\ChangeDetectionStrategy\StrategyWithFlushDeletedOnPathInterface; use Neos\Flow\Monitor\ChangeDetectionStrategy\StrategyWithMarkDeletedInterface; use Neos\Flow\SignalSlot\Dispatcher; use Neos\Utility\Files; @@ -81,6 +82,7 @@ class FileMonitor /** * Array of directories and files that were cached on the last run. * + * @deprecated to be replaced by only supporting {@see StrategyWithFlushDeletedOnPathInterface} * @var array */ protected $directoriesAndFiles = null; @@ -303,7 +305,9 @@ protected function detectChangesOnPath($path, $filenamePattern) $this->changedPaths[$path] = ChangeDetectionStrategyInterface::STATUS_CREATED; } + $currentSubDirectoriesAndFilesMask = []; foreach ($currentSubDirectoriesAndFiles as $pathAndFilename) { + $currentSubDirectoriesAndFilesMask[$pathAndFilename] = 1; $status = $this->changeDetectionStrategy->getFileStatus($pathAndFilename); if ($status !== ChangeDetectionStrategyInterface::STATUS_UNCHANGED) { $this->changedFiles[$pathAndFilename] = $status; @@ -316,18 +320,27 @@ protected function detectChangesOnPath($path, $filenamePattern) $nowDetectedFilesAndDirectories[$pathAndFilename] = 1; } - if ($this->directoriesAndFiles[$path] !== []) { - foreach (array_keys($this->directoriesAndFiles[$path]) as $pathAndFilename) { - $this->changedFiles[$pathAndFilename] = ChangeDetectionStrategyInterface::STATUS_DELETED; - if ($this->changeDetectionStrategy instanceof StrategyWithMarkDeletedInterface) { - $this->changeDetectionStrategy->setFileDeleted($pathAndFilename); - } else { - // This call is needed to mark the file deleted in any possibly existing caches of the strategy. - // The return value is not important as we know this file doesn't exist so we set the status to DELETED anyway. - $this->changeDetectionStrategy->getFileStatus($pathAndFilename); + if ($this->changeDetectionStrategy instanceof StrategyWithFlushDeletedOnPathInterface) { + $deletedFiles = $this->changeDetectionStrategy->flushDeletedOnPath($path, $currentSubDirectoriesAndFilesMask); + if ($deletedFiles) { + $this->changedFiles = [...$this->changedFiles, ...$deletedFiles]; + $currentDirectoryChanged = true; + } + } else { + // legacy deletion detection + if ($this->directoriesAndFiles[$path] !== []) { + foreach (array_keys($this->directoriesAndFiles[$path]) as $pathAndFilename) { + $this->changedFiles[$pathAndFilename] = ChangeDetectionStrategyInterface::STATUS_DELETED; + if ($this->changeDetectionStrategy instanceof StrategyWithMarkDeletedInterface) { + $this->changeDetectionStrategy->setFileDeleted($pathAndFilename); + } else { + // This call is needed to mark the file deleted in any possibly existing caches of the strategy. + // The return value is not important as we know this file doesn't exist so we set the status to DELETED anyway. + $this->changeDetectionStrategy->getFileStatus($pathAndFilename); + } } + $currentDirectoryChanged = true; } - $currentDirectoryChanged = true; } if ($currentDirectoryChanged) {