From 8cc25cd1ac4071ac78d65f263d76126daed4fb3e Mon Sep 17 00:00:00 2001 From: ziegler Date: Mon, 9 Oct 2023 13:24:45 +0200 Subject: [PATCH] Added cache cleaning logic and blocking middleware --- service/src/main/php/bootstrap.php | 9 +- service/src/main/php/cacheCleaner.php | 8 ++ .../main/php/src/lib/BlockingMiddleware.php | 41 ++++++ service/src/main/php/src/lib/CacheCleaner.php | 136 ++++++++++++++++++ service/src/main/php/src/lib/Logger.php | 21 +-- service/src/main/php/src/tools/h5p/H5P.php | 26 ++-- 6 files changed, 220 insertions(+), 21 deletions(-) create mode 100644 service/src/main/php/cacheCleaner.php create mode 100644 service/src/main/php/src/lib/BlockingMiddleware.php create mode 100644 service/src/main/php/src/lib/CacheCleaner.php diff --git a/service/src/main/php/bootstrap.php b/service/src/main/php/bootstrap.php index b1504f2..a01aed0 100644 --- a/service/src/main/php/bootstrap.php +++ b/service/src/main/php/bootstrap.php @@ -1,17 +1,20 @@ [ +$container = new Container; +$app = new App([$container, 'settings' => [ 'displayErrorDetails' => true, 'debug' => true, 'whoops.editor' => 'sublime', @@ -31,6 +34,8 @@ return $view; }; +$app->add(new BlockingMiddleware()); + $app->get('/', function (Request $request, Response $response) { $this->get('log')->info($request->getUri()); $connector = new Connector($this->get('log'), $this, $response); diff --git a/service/src/main/php/cacheCleaner.php b/service/src/main/php/cacheCleaner.php new file mode 100644 index 0000000..ad4b47b --- /dev/null +++ b/service/src/main/php/cacheCleaner.php @@ -0,0 +1,8 @@ +run(); diff --git a/service/src/main/php/src/lib/BlockingMiddleware.php b/service/src/main/php/src/lib/BlockingMiddleware.php new file mode 100644 index 0000000..bad920b --- /dev/null +++ b/service/src/main/php/src/lib/BlockingMiddleware.php @@ -0,0 +1,41 @@ + + */ +class BlockingMiddleware +{ + /** + * Function __invoke + * + * Function to run the middleware as per the Slim PHP documentation + * + * @param Request $request + * @param Response $response + * @param callable $next + * @return Response + */ + public function __invoke(Request $request, Response $response, callable $next): Response + { + $retryCount = 0; + while (file_exists(__DIR__ . '/' . CacheCleaner::LOCK_FILE_NAME) && $retryCount <= 10) { + sleep(1); + $retryCount += 1; + } + if (file_exists(__DIR__ . '/' . CacheCleaner::LOCK_FILE_NAME)) { + return $response->withStatus(503); + } + return $next($request, $response); + } +} diff --git a/service/src/main/php/src/lib/CacheCleaner.php b/service/src/main/php/src/lib/CacheCleaner.php new file mode 100644 index 0000000..e074519 --- /dev/null +++ b/service/src/main/php/src/lib/CacheCleaner.php @@ -0,0 +1,136 @@ + + */ +class CacheCleaner +{ + public const LOCK_FILE_NAME = 'LOCK'; + private MonoLogger $logger; + private Database $database; + private H5P $h5p; + + /** + * CacheCleaner constructor + */ + public function __construct() { + $this->init(); + } + + /** + * Function init + * + * sets up dependencies + */ + private function init(): void { + $logInitializer = new Logger(); + $this->logger = $logInitializer->getLog(); + $this->database = new Database(); + $this->h5p = new H5P(); + } + + /** + * Function run + * + * This is the main workhorse of the class + * + * @return void + */ + public function run(): void { + $this->logger->info('### Cache cleaner started ###'); + try { + $this->lock(); + $this->database->beginTransaction(); + $this->clearH5pTables(); + $this->clearH5pDirectories(); + ! $this->database->commit() && throw new Exception('### Database commit failed. Script terminated ###'); + } catch (Exception $exception) { + $this->logger->error($exception->getMessage()); + $this->logger->error('### Database transaction rollback started. ###'); + $this->database->rollBack(); + } finally { + try { + $this->unlock(); + $this->logger->info('### Cache cleaner ran successfully ###'); + } catch (Exception $exception) { + $this->logger->error($exception->getMessage()); + } + } + } + + /** + * Function clearH5pTables + * + * clears H5P-related tables + * + * @throws Exception + */ + private function clearH5pTables(): void { + $libraryLanguageRes = $this->database->query('TRUNCATE TABLE h5p_libraries_languages'); + $contentsLibrariesRes = $this->database->query('TRUNCATE TABLE h5p_contents_libraries'); + $contentsRes = $this->database->query('TRUNCATE TABLE h5p_contents'); + $librariesRes = $this->database->query('TRUNCATE TABLE h5p_libraries'); + $librariesLanguagesRes = $this->database->query('TRUNCATE TABLE h5p_libraries_languages'); + $librariesLibrariesRes = $this->database->query('TRUNCATE TABLE h5p_libraries_libraries'); + if (in_array(false, [$libraryLanguageRes, $contentsLibrariesRes, $contentsRes, $librariesRes, $librariesLanguagesRes, $librariesLibrariesRes], true)) { + throw new Exception('### Cache cleaner error: Database operation failed. Script will be terminated. ###'); + } + } + + /** + * Function clearH5pDirectories + * + * deletes all files and folders from the H5P-related directories within the cache directory + * + * @throws Exception + */ + private function clearH5pDirectories(): void { + $directories = ['content', 'exports', 'libraries', 'editor', 'temp']; + try { + foreach ($directories as $directory) { + $this->h5p->rrmdir($this->h5p->H5PFramework->get_h5p_path() . '/' . $directory, true); + } + } catch (Exception $exception) { + throw new Exception('### Cache cleaner error: ' . $exception->getMessage() . '. Script will be terminated. ###'); + } + } + + /** + * Function lock + * + * creates a lock file in order to lock the connector service + * for the duration of the cache cleaning process + * + * @throws Exception + */ + private function lock(): void { + $lockFile = fopen(static::LOCK_FILE_NAME, "w"); + $lockFile === false && throw new Exception('### Cache cleaner error: Cannot create lock file. Script will be terminated. ###'); + } + + /** + * Function unlock + * + * deletes the lock file in order to unlock the connector service + * + * @throws Exception + */ + private function unlock(): void { + $isFileDeleted = unlink(static::LOCK_FILE_NAME); + ! $isFileDeleted && throw new Exception('### Cache cleaner error: Cannot delete lock file. Script will be terminated. ###'); + } +} diff --git a/service/src/main/php/src/lib/Logger.php b/service/src/main/php/src/lib/Logger.php index 4df3994..13d7d40 100644 --- a/service/src/main/php/src/lib/Logger.php +++ b/service/src/main/php/src/lib/Logger.php @@ -2,30 +2,35 @@ namespace connector\lib; +use Exception; use Monolog\Handler\RedisHandler; use Monolog\Formatter\LogstashFormatter; +use Monolog\Handler\RotatingFileHandler; +use Monolog\Handler\StreamHandler; +use Monolog\Logger as MonoLogger; +use Monolog\Processor\IntrospectionProcessor; use Predis\Client; class Logger { - private $log; + private MonoLogger $log; public function __construct() { - $this->log = new \Monolog\Logger('eduConnector'); - $this->log->pushProcessor(new \Monolog\Processor\IntrospectionProcessor()); + $this->log = new MonoLogger('eduConnector'); + $this->log->pushProcessor(new IntrospectionProcessor()); /* * Log to local file * */ if(LOG_MODE === 'file') { - $this->log->pushHandler(new \Monolog\Handler\RotatingFileHandler(DATA . DIRECTORY_SEPARATOR . 'log' . DIRECTORY_SEPARATOR . 'error.log', 0, \Monolog\Logger::ERROR)); - $this->log->pushHandler(new \Monolog\Handler\RotatingFileHandler(DATA . DIRECTORY_SEPARATOR . 'log' . DIRECTORY_SEPARATOR . 'info.log', 0, \Monolog\Logger::INFO)); + $this->log->pushHandler(new RotatingFileHandler(DATA . DIRECTORY_SEPARATOR . 'log' . DIRECTORY_SEPARATOR . 'error.log', 0, MonoLogger::ERROR)); + $this->log->pushHandler(new RotatingFileHandler(DATA . DIRECTORY_SEPARATOR . 'log' . DIRECTORY_SEPARATOR . 'info.log', 0, MonoLogger::INFO)); } else if(LOG_MODE === 'stdout') { - $this->log->pushHandler(new \Monolog\Handler\StreamHandler('php://stdout', \Monolog\Logger::INFO)); - $this->log->pushHandler(new \Monolog\Handler\StreamHandler('php://stderr', \Monolog\Logger::ERROR)); + $this->log->pushHandler(new StreamHandler('php://stdout', MonoLogger::INFO)); + $this->log->pushHandler(new StreamHandler('php://stderr', MonoLogger::ERROR)); } else { - throw new \Exception("invalid LOG_MODE: " . LOG_MODE); + throw new Exception("invalid LOG_MODE: " . LOG_MODE); } $this->log->info("Logger started in mode " . LOG_MODE); /* diff --git a/service/src/main/php/src/tools/h5p/H5P.php b/service/src/main/php/src/tools/h5p/H5P.php index e51a0d2..914534a 100644 --- a/service/src/main/php/src/tools/h5p/H5P.php +++ b/service/src/main/php/src/tools/h5p/H5P.php @@ -3,7 +3,7 @@ use connector\lib\Database; use connector\lib\EduRestClient; -use Dompdf\Exception; +use Exception; use Slim\Http\Response; define('MODE_NEW', 'mode_new'); @@ -140,18 +140,22 @@ private function copyr($source, $dest) { } - public function rrmdir($dir) { - if (is_dir($dir)) { - $objects = scandir($dir); - foreach ($objects as $object) { + /** + * @throws Exception + */ + public function rrmdir(string $dir, bool $throwException = false): void { + if (is_dir($dir)) { + $objects = scandir($dir); + foreach ($objects as $object) { if ($object != "." && $object != "..") { - if (is_dir($dir."/".$object)) - $this -> rrmdir($dir."/".$object); - else - unlink($dir."/".$object); - } + if (is_dir($dir."/".$object)) + $this -> rrmdir($dir."/".$object); + else + unlink($dir."/".$object); + } } - rmdir($dir); + $isDirDeleted = rmdir($dir); + ! $isDirDeleted && $throwException && throw new Exception('Cannot delete directory: ' . $dir . '.'); } }