Commit a5a6ead1 by Dirk Benkert

added jobRunner,

use job runner to auto run jobs configured in config['jobs']['autorun']

example implementation of the cleanup job (running as ConsoleService and AutoRun)
example implementation of validate-setup job running as ConsoleService

Implementation of a Task (a Job is a Group of Task)
parent bcf6bf1f
......@@ -32,20 +32,31 @@ rename config/autoload/local.php.dist to config/autoload/local.php and edit with
),
```
enter you database credentials and modify the path where the uploaded files are stored if necessary
enter you database credentials and modify the path where the uploaded files are stored if necessary.
Make shure "physical-path" is writeable.
Make shure data/* is writeable
You can call the cleanup task via cron or by manually executing
```
php index.php cleanup
```
The application also checks if cleanup has been called recently and if not executes the task.
# Setup
after that you should be able to access the application with your web browser and see the login page.
You should be able to access the application with your web browser and see the login page now.
To set up the database and insert initial data run the following commands.
```
vendor/bin/doctrine-module orm:schemat-tool:create
vendor/bin/doctrine-module data-fixture:import --append
php index.php orm:schemat-tool:create
php index.php data-fixture:import --append
```
**If you use data-fixture:import without --append all data in your database will be purged!**
This data-fixture import creates a single user "admin" with password "admin".
You can now login and access User administration to create a user and set your own password.
Delete the admin user after you created a user for yourself.
To validate the setup of run
```
php index.php validate-setup
```
......@@ -118,6 +118,7 @@ class Module {
if (!$e->getViewModel()->acl->isAllowed($userRole, $route)) {
// sends User to 404 error page making missing permissions look like the page does not exists
// which is fine for now
error_log($ex);
$response = $e->getResponse();
$response->setStatusCode(404);
}
......@@ -127,6 +128,8 @@ class Module {
error_log($ex);
$response = $e->getResponse();
$response->setStatusCode(404);
} catch (\Exception $ex) {
error_log($ex);
}
}
......
......@@ -167,6 +167,7 @@ class Module {
if (!$e->getViewModel()->acl->isAllowed($userRole, $route)) {
// sends User to 404 error page making missing permissions look like the page does not exists
// which is fine for now
error_log($ex);
$response = $e->getResponse();
$response->setStatusCode(404);
}
......@@ -176,6 +177,8 @@ class Module {
error_log($ex);
$response = $e->getResponse();
$response->setStatusCode(404);
} catch (\Exception $ex) {
error_log($ex);
}
}
......
......@@ -46,7 +46,7 @@ class File extends \Administration\CrudPersistor\Entity {
//$path = rtrim($basePath, '/') . DIRECTORY_SEPARATOR . $data['folder']->label . DIRECTORY_SEPARATOR . $data['upload']['name'];
$pathService = new Path($this->getService()->getController()->getServiceLocator());
$path = $pathService->filePath($data['folder']->label, $data['upload']['name']);
$path = $pathService->getFilePath($data['folder']->label, $data['upload']['name']);
if (!is_dir(dirname($path))) {
mkdir(dirname($path));
......
......@@ -8,7 +8,7 @@ class Folder extends \Administration\CrudPersistor\Entity {
public function remove($entity) {
$controller = $this->getService()->getController();
$pathService = new Path($this->getService()->getController()->getServiceLocator());
rmdir($pathService->filePath($entity->label));
rmdir($pathService->getFilePath($entity->label));
$controller->getEntityManager()->remove($entity);
$controller->getEntityManager()->flush();
......
<?php
/**
* Description for User
*
* @package Application_Entity
* @author Dirk Benkert <mail@dirk-benkert.de>
* @copyright Copyright (c) Dirk Benkert
* @version 1.0
*/
namespace Application\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
use \DateTime;
/**
* @ORM\Entity
* @ORM\Table(name="job")
*/
class Job extends AbstractEntity {
/**
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
* @ORM\Column(type="integer")
*/
protected $id;
/**
* @ORM\Column(type="string", nullable=true)
*/
protected $name;
/**
* @ORM\Column(type="datetime", nullable=true)
*/
protected $executed;
public function setExecuted($timestamp) {
$this->executed = new DateTime();
$this->executed->setTimestamp($timestamp);
}
}
......@@ -121,7 +121,7 @@ class FileForm extends AbstractForm {
$folder = $entityManager->find('Application\Entity\Folder', $this->getFolderId());
$pathService = new Path($this->getServiceLocator());
$path = $pathService->filePath($folder->label);
$path = $pathService->getFilePath($folder->label);
$entityUnique = new FileEntityUnique(
$this->getServiceLocator(),
......
......@@ -14,10 +14,8 @@ abstract class AbstractService {
*
* @return void
*/
public function __construct(ServiceLocatorInterface $serviceLocator = null) {
if (null !== $serviceLocator) {
$this->setServiceLocator($serviceLocator);
}
public function __construct(ServiceLocatorInterface $serviceLocator) {
$this->setServiceLocator($serviceLocator);
}
public function getServiceLocator() {
......
<?php
namespace Application\Service;
class Path extends AbstractService {
public function filePath($foldername = '', $filename = '') {
public function getBasePath() {
$serviceManager = $this->getServiceLocator();
$config = $serviceManager->get('config');
$basePath = $config['share-application']['physical-path'];
if (!isset($config['share-application']['physical-path'])) {
throw \RuntimeException("\$config[\'share-application\'][\'physical-path\'] is not set in configuration.");
}
return $config['share-application']['physical-path'];
}
public function getFilePath($foldername = '', $filename = '') {
$basePath = $this->getBasePath();
$path = rtrim($basePath, '/') . DIRECTORY_SEPARATOR
. $foldername . DIRECTORY_SEPARATOR
. $filename;
return $path;
}
public function getPermissions($path) {
$perms = fileperms($path);
return $this->permissionsToString($perms);
}
protected function permissionsToString($perms) {
if (($perms & 0xC000) == 0xC000) {
// Socket
$info = 's';
} elseif (($perms & 0xA000) == 0xA000) {
// Symbolischer Link
$info = 'l';
} elseif (($perms & 0x8000) == 0x8000) {
// Regulär
$info = '-';
} elseif (($perms & 0x6000) == 0x6000) {
// Block special
$info = 'b';
} elseif (($perms & 0x4000) == 0x4000) {
// Verzeichnis
$info = 'd';
} elseif (($perms & 0x2000) == 0x2000) {
// Character special
$info = 'c';
} elseif (($perms & 0x1000) == 0x1000) {
// FIFO pipe
$info = 'p';
} else {
// Unknown
$info = 'u';
}
// owner
$info .= (($perms & 0x0100) ? 'r' : '-');
$info .= (($perms & 0x0080) ? 'w' : '-');
$info .= (($perms & 0x0040) ?
(($perms & 0x0800) ? 's' : 'x' ) :
(($perms & 0x0800) ? 'S' : '-'));
// group
$info .= (($perms & 0x0020) ? 'r' : '-');
$info .= (($perms & 0x0010) ? 'w' : '-');
$info .= (($perms & 0x0008) ?
(($perms & 0x0400) ? 's' : 'x' ) :
(($perms & 0x0400) ? 'S' : '-'));
// other
$info .= (($perms & 0x0004) ? 'r' : '-');
$info .= (($perms & 0x0002) ? 'w' : '-');
$info .= (($perms & 0x0001) ?
(($perms & 0x0200) ? 't' : 'x' ) :
(($perms & 0x0200) ? 'T' : '-'));
return $info;
}
}
......@@ -11,21 +11,51 @@
namespace Cli;
use Zend\ModuleManager\Feature\AutoloaderProviderInterface;
use Zend\ModuleManager\Feature\BootstrapListenerInterface;
use Zend\EventManager\EventInterface;
use Zend\ModuleManager\Feature\ConfigProviderInterface;
use Zend\ModuleManager\Feature\ConsoleUsageProviderInterface;
use Zend\ModuleManager\Feature\ConsoleBannerProviderInterface;
use Zend\Console\Adapter\AdapterInterface as Console;
use Cli\Controller\AbstractConsoleController;
use Zend\Mvc\MvcEvent;
class Module implements
AutoloaderProviderInterface,
BootstrapListenerInterface,
ConfigProviderInterface,
ConsoleBannerProviderInterface,
ConsoleUsageProviderInterface {
public function onBootstrap(EventInterface $event) {
$app = $event->getApplication();
$eventManager = $app->getEventManager()->getSharedManager();
$serviceManager = $app->getServiceManager();
// attach to Cli Modules dispatch event. Dispatching to an other module leaves this untouched
$eventManager->attach('Cli', 'dispatch', function($e) {
$controller = $e->getTarget();
if ($controller instanceof AbstractConsoleController) {
$controller->assertCliRequest();
}
}, 100);
$config = $serviceManager->get('Config');
if (isset($config['jobs']['autorun'])) {
// attach to the Application modules dispatch event
$eventManager->attach('Application', 'dispatch', function($e) {
$controller = $e->getTarget();
$jobRunner = $controller->getServiceLocator()->get('Cli\JobRunner');
$jobRunner->runJobs();
}, 90);
}
}
public function getConsoleUsage(Console $console) {
return array(
// Describe available commands
'cleanup' => 'start the cleanup process',
'validate-setup' => 'check if setup is ok',
// Describe expected parameters
array(),
array(),
......
......@@ -6,10 +6,24 @@ return array(
'Cli\Controller\Index' => 'Cli\Controller\IndexController',
),
),
'service_manager' => array(
'factories' => array(
'Cli\JobRunner' => 'Cli\Factory\JobRunnerFactory',
),
),
'jobs' => array(
'verbose' => false,
'autorun' => array(
'cleanup' => array(
'classname' => 'Cli\Job\Cleanup',
'execute' => 'daily', // or use seconds e.g. 86400 for daily execution
),
),
),
'console' => array(
'router' => array(
'routes' => array(
'cli' => array(
'cleanup' => array(
'options' => array(
// add [ and ] if optional)
'route' => 'cleanup',
......@@ -19,6 +33,16 @@ return array(
),
),
),
'validate-setup' => array(
'options' => array(
// add [ and ] if optional)
'route' => 'validate-setup',
'defaults' => array(
'controller' => 'Cli\Controller\Index',
'action' => 'validate-setup'
),
),
)
),
),
),
......
<?php
/**
* Description for IndexControll
*
* @package Application_Controller
* @author Dirk Benkert <dirk.benkert.extern@vogel-druck.de>
* @copyright Copyright (c) Vogel-Druck
* @version 1.0
*/
namespace Cli\Controller;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\Console\Request as ConsoleRequest;
use Doctrine\ORM\EntityManager as EntityManager;
use Cli\NotCommandLineException;
use Application\Util\Text;
abstract class AbstractConsoleController extends AbstractActionController {
/**
* Entity Manager
*
* @var Doctrine\ORM\EntityManager
*/
protected $entityManager = null;
public function assertCliRequest() {
$request = $this->getRequest();
// Make sure that we are running in a console and the user has not tricked our
// application into running this action from a public web server.
if (!$request instanceof ConsoleRequest){
throw new NotCommandLineException(Text::translate('You can only use this action from the console!'));
}
}
/**
* returns the doctrine entity manager via service manager
*
* @return Doctrine\ORM\EntityManager
*/
public function getEntityManager() {
if (null === $this->entityManager) {
$this->setEntityManager($this->getServiceLocator()->get('Doctrine\ORM\EntityManager'));
}
return $this->entityManager;
}
/**
* sets the entity manager
*
* @param \Doctrine\ORM\EntityManager $entityManager
*/
public function setEntityManager(\Doctrine\ORM\EntityManager $entityManager) {
$this->entityManager = $entityManager;
}
}
\ No newline at end of file
......@@ -11,29 +11,18 @@
namespace Cli\Controller;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\Console\Request as ConsoleRequest;
use Zend\Console\Adapter\AdapterInterface as Console;
use Cli\NotCommandLineException;
use Cli\Service\ExpiredFolders;
use Cli\Service\UnusedUsers;
class IndexController extends AbstractActionController {
use Cli\Service\Cleanup;
use Cli\Service\ValidateSetup;
use Application\Entity\Job;
class IndexController extends AbstractConsoleController {
public function cleanupAction() {
$request = $this->getRequest();
// Make sure that we are running in a console and the user has not tricked our
// application into running this action from a public web server.
if (!$request instanceof ConsoleRequest){
throw new NotCommandLineException('You can only use this action from a console!');
}
$console = $this->getServiceLocator()->get('console');
$folders = new ExpiredFolders($this->getServiceLocator());
$folders->cleanup();
$service = new Cleanup($this->getServiceLocator());
$service->run();
}
$users = new UnusedUsers($this->getServiceLocator());
$users->cleanup();
public function validateSetupAction() {
$service = new ValidateSetup($this->getServiceLocator());
$service->validate();
}
}
<?php
/**
* File description
*
* @category CATEGORY
* @package PACKAGE
* @subpackage SUBPACKAGE
* @copyright Copyright (c) 2012 Dirk Benkert
* @author Dirk Benkert <benkert.dirk@gmail.com>
* @license Dirk Benkert standard license terms and conditions 1.0
*/
namespace Cli\Factory;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use Cli\JobRunner;
class JobRunnerFactory implements FactoryInterface {
public function createService(ServiceLocatorInterface $serviceLocator) {
return new JobRunner($serviceLocator);
}
}
<?php
namespace Cli\Job;
use Cli\Task\AbstractTask;
use Cli\Task\DelayedTaskInterface;
use Cli\Task\CleanExpiredFolders;
use Cli\Task\CleanUnusedUsers;
class Cleanup extends AbstractTask implements DelayedTaskInterface {
protected $jobname = array(
'cleanup' => 'cleanup',
);
protected $job = null;
public function run() {
$job = $this->getJob();
$folders = new CleanExpiredFolders($this->getServiceLocator());
$folders->run();
$this->addMessages($folders->getMessages());
$users = new CleanUnusedUsers($this->getServiceLocator());
$users->run();
$this->addMessages($users->getMessages());
$em = $this->getEntityManager();
$job->setExecuted(time());
$em->persist($job);
$em->flush();
}
/**
*
* @return int Timestamp
*/
public function getLastExecutionTimestamp() {
$job = $this->getJob();
return $job->executed->format('U');
}
public function getJob() {
if (null === $this->job) {
$em = $this->getEntityManager();
$this->job = $em->getRepository('Application\Entity\Job')->findOneBy(array('name' => $this->jobname['cleanup']));
if (!$this->job) {
$this->job = new Job();
$this->job->setData(array(
'name' => $this->jobname['cleanup']
));
}
}
return $this->job;
}
}
<?php
namespace Cli;
use Zend\ServiceManager\ServiceLocatorAwareInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use Cli\Task\DelayedTaskInterface;
class JobRunner {
protected $serviceLocator = null;
protected $intervals = array(
'hourly' => 3600,
'daily' => 86400,
'weekly' => 604800,
'monthly' => 18144000,
);
/**
* The constructor
*
* @return void
*/
public function __construct(ServiceLocatorInterface $serviceLocator) {
$this->setServiceLocator($serviceLocator);
}
public function runJobs() {
$config = $this->getServiceLocator()->get('Config');
$verbose = false;
if (isset($config['jobs']['verbose'])) {
$verbose = $config['jobs']['verbose'];
}
if (isset($config['jobs']['autorun'])) {
foreach ($config['jobs']['autorun'] as $name => $options) {
$this->runJob($name, $options, $verbose);
}
}
}
public function runJob($name, $options, $verbose) {
$job = new $options['classname']($this->getServiceLocator());
if ($job instanceof DelayedTaskInterface) {
$interval = $options['execute'];
if (!is_numeric($interval) && isset($this->intervals[$options['execute']])) {
$interval = $this->intervals[$options['execute']];
} else {
throw new \RuntimeException('unknown execution interval: "' . $interval . '"');
}
if (time() - $job->getLastExecutionTimestamp() > $interval) {
$job->run();
} else {
if ($verbose) {
$job->addMessage($name . ' not executed. Last execution time within interval');
}
}
} else {
$job->run();
}
if ($verbose) {
$this->writeToLog($job->getMessages());
}
}
public function writeToLog($messages) {
foreach ($messages as $message) {
error_log($message);
}
}
public function getServiceLocator() {
return $this->serviceLocator;
}
public function setServiceLocator($serviceLocator) {
$this->serviceLocator = $serviceLocator;
}
}
<?php
namespace Cli\Service;
use Application\Service\AbstractService;
use Zend\Console\Adapter\AdapterInterface as Console;
use Zend\ServiceManager\ServiceLocatorInterface;
abstract class AbstractConsoleService extends AbstractService {
/**
* console
*
* @var Console
*/
protected $console = null;
/**
* The constructor
*
* @return void
*/
public function __construct(ServiceLocatorInterface $serviceLocator) {
parent::__construct($serviceLocator);
$this->setConsole($this->getServiceLocator()->get('console'));
}
public function getConsole() {
return $this->console;
}
public function setConsole(Console $console) {
$this->console = $console;
}
public function writeConsole($messages) {
$console = $this->getConsole();
$console->writeLine('');
foreach ($messages as $line) {
$console->writeLine($line);
}
}
}
<?php
namespace Cli\Service;
use Cli\Job\Cleanup as CleanupJob;
class Cleanup extends AbstractConsoleService {
public function run() {
$this->getConsole()->writeLine('');
$this->getConsole()->writeLine('');
$this->getConsole()->writeLine('');
//cleanup job is called via console and random http request, therefore i had to decouple from console
$cleanup = new CleanupJob($this->getServiceLocator());
$cleanup->run();
$this->writeConsole($cleanup->getMessages());
}
}
<?php
namespace Cli\Service;
use Application\Service\AbstractService;
use Zend\ServiceManager\ServiceLocatorInterface;
class UnusedUsers extends AbstractService {
protected $console = null;
/**
* The constructor
*
* @return void
*/
public function __construct(ServiceLocatorInterface $serviceLocator = null) {
parent::__construct($serviceLocator);
$this->console = $this->getServiceLocator()->get('console');
}
public function cleanup() {
$em = $this->getEntityManager();
$users = $em->getRepository('Application\Entity\User')->getUnusedUsers();
foreach ($users as $user) {
$em->remove($user);
}
$this->console->writeLine('deleting invited users');
$this->console->writeLine(count($users) . ' unused users removed.');
$em->flush();
}
}
<?php
namespace Cli\Service;
use Zend\Console\ColorInterface;
use Application\Service\Path;
class ValidateSetup extends AbstractConsoleService {
public function validate() {
$configPath = getcwd() . '/config/autoload/local.php';
$this->getConsole()->writeLine('validating setup ...');
$this->getConsole()->writeLine('reading configuration from ' . $configPath);
// check physical path exists
$path = new Path($this->getServiceLocator());
$basePath = realpath($path->getBasePath());
$this->getConsole()->writeLine('');
$this->getConsole()->writeLine('check that ' . $basePath . ' exists.');
if (!is_dir($basePath)) {
$this->getConsole()->writeLine('path does not exist.', ColorInterface::RED);
} else {
$this->getConsole()->writeLine('[OK]', ColorInterface::LIGHT_GREEN);
}
// check physical path is writeable
$this->getConsole()->writeLine('');
$this->getConsole()->writeLine('check that ' . $basePath .</