...
 
Commits (2)
......@@ -49,3 +49,9 @@ services:
- %publish_path_root%
tags:
- { name: kernel.event_listener, event: kernel.exception, method: onKernelException }
lockfile_handler:
class: AppBundle\Utils\LockFileHandler
arguments:
- '%kernel.cache_dir%'
- 1800
......@@ -7,6 +7,7 @@ use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use AppBundle\Model\Library;
use Symfony\Component\HttpKernel\Exception\ConflictHttpException;
class PublishController extends Controller {
......@@ -23,10 +24,23 @@ class PublishController extends Controller {
/**
* @Route("/admin/publish{identifier}" , requirements={"identifier": ".*"})
*
* @param $identifier
*
* @return Response
*/
public function PublishAction(Request $request, $identifier) {
public function PublishAction($identifier) {
$lockFileHandler = $this->get('lockfile_handler');
$this->publisher = $this->get('publisher');
$bookSlug = Library::parseIdentifier($identifier)['bookSlug'];
if ($lockFileHandler->exists($bookSlug)) {
$err = 'Publishing is already in process, please try again later';
throw new ConflictHttpException($err);
}
$lockFileHandler->create($bookSlug);
if ($bookSlug) {
$this->publishSuccess = $this->publisher->publish($identifier);
}
......@@ -38,8 +52,12 @@ class PublishController extends Controller {
. "to really slow down the server. If you want to publish all books "
. "you can run 'docs:publish' from the command line interface.");
}
$content['identifier'] = trim($identifier, "/");
$content['messages'] = $this->publisher->getMessages();
$lockFileHandler->release($bookSlug);
return $this->render('AppBundle:Publish:publish.html.twig', $content);
}
......
<?php
namespace AppBundle\Utils;
class LockFileHandler {
/**
* @var string
* Where the lock files will be stored.
*/
protected $lockDirectory;
/**
* @var int
* How many seconds a lock remains active
*/
protected $lockLifetimeInSeconds;
/**
* @param $cacheDir
* The cache directory for the current environment
* @param $lockLifetimeInSeconds
* How many seconds a lock remains active
*/
public function __construct($cacheDir, $lockLifetimeInSeconds)
{
$lockDirectory = $cacheDir . '/locks';
if (!is_dir($lockDirectory)) {
mkdir($lockDirectory, 0777, true);
}
$this->lockDirectory = $lockDirectory;
$this->lockLifetimeInSeconds = (int) $lockLifetimeInSeconds;
}
/**
* Creates a lock file or updates an existing one
*
* @param string $name
*/
public function create($name) {
$name = trim($name);
if (!$name) {
throw new \Exception('Lock file name cannot be empty');
}
$lockFilename = $this->getFullFilename($name);
touch($lockFilename);
}
/**
* Check if a lock file exists. Removes it if it is expired.
*
* @param string $name
* @return bool
*/
public function exists($name) {
$lockFilename = $this->getFullFilename($name);
$exists = file_exists($lockFilename);
if ($exists && $this->isExpired($lockFilename)) {
$this->release($name);
$exists = FALSE;
}
return $exists;
}
/**
* Removes a lock file. Non-existing locks are ignored
*
* @param $name
*/
public function release($name) {
$lockFilename = $this->getFullFilename($name);
if (!file_exists($lockFilename)) {
return;
}
unlink($lockFilename);
}
/**
* @param $name
*
* @return string
*/
protected function getFullFilename($name)
{
$lockFilename = sprintf('%s/%s', $this->lockDirectory, $name);
return $lockFilename;
}
/**
* Checks if a lock file was last modified in the last LOCK_EXPIRY seconds
*
* @param $lockFilename
* The full filename
*
* @return bool
*/
protected function isExpired($lockFilename)
{
$lastModified = filemtime($lockFilename);
$expiresAt = $lastModified + $this->lockLifetimeInSeconds;
$now = time();
return $now > $expiresAt;
}
}
<?php
namespace AppBundle\Tests\Utils;
use AppBundle\Utils\LockFileHandler;
class LockFileHandlerTest extends \PHPUnit_Framework_TestCase
{
/**
* @var string
*/
protected $lockDir;
/**
* @test
*/
public function canCreateLockFile() {
$handler = $this->getHandler();
$handler->create('foo');
$this->assertTrue($handler->exists('foo'));
}
/**
* @test
*/
public function creationAndDeletionWillWork() {
$handler = $this->getHandler();
$handler->create('foo');
$handler->release('foo');
$this->assertFalse($handler->exists('foo'));
}
/**
* @test
*/
public function nonExistingLockWillReturnFalse() {
$handler = $this->getHandler();
$this->assertFalse($handler->exists('foo'));
}
/**
* @test
*/
public function releaseWithNonExistingLockWillDoNothing() {
$this->getHandler()->release('foo');
}
/**
* @test
*/
public function createWithExistingLockWillDoNothing() {
$handler = $this->getHandler();
$handler->create('foo');
$handler->create('foo');
$this->assertTrue($handler->exists('foo'));
}
/**
* @test
*/
public function expiryWillWork() {
$handler = $this->getHandler(0);
$handler->create('foo');
sleep(1);
$this->assertFalse($handler->exists('foo'));
}
/**
* @test
*/
public function recreatingALockWillResetExpiry() {
$handler = $this->getHandler(1);
$handler->create('foo');
sleep(2);
$handler->create('foo'); // first lock would have expired
$this->assertTrue($handler->exists('foo'));
}
/**
* @param int $lockTTL
* How long the lock will remain active
*
* @return LockFileHandler
*/
private function getHandler($lockTTL = 5) {
return new LockFileHandler($this->lockDir, $lockTTL);
}
/**
* Create the lock directory in temp directory
*/
protected function setUp() {
$this->lockDir = sys_get_temp_dir() . '/civicrm-docs-test-locks';
}
/**
* Removes all locks in the temp lock directory
*/
protected function tearDown() {
array_map('unlink', glob($this->lockDir . '/locks/*'));
}
}