extract and fix lock methods

This commit is contained in:
Jean-Christian Paul Denis 2023-05-13 00:25:15 +02:00
parent 7b0460f23b
commit 192698148b
Signed by: JcDenis
GPG Key ID: 1B5B8C5B90B6C951
4 changed files with 162 additions and 44 deletions

View File

@ -17,5 +17,4 @@ if (!defined('DC_RC_PATH')) {
class initActivityReport class initActivityReport
{ {
public const ACTIVITY_TABLE_NAME = 'activity'; public const ACTIVITY_TABLE_NAME = 'activity';
public const CACHE_DIR_NAME = 'activityreport';
} }

View File

@ -61,8 +61,8 @@ class ActivityReport
/** @var ActivityReport $instance ActivityReport instance */ /** @var ActivityReport $instance ActivityReport instance */
private static $instance; private static $instance;
/** @var null|resource $lock File lock for update */ /** @var null|string $lock File lock for update */
private $lock = null; private static $lock = null;
/** /**
* Constructor sets activity main type. * Constructor sets activity main type.
@ -495,75 +495,49 @@ class ActivityReport
} }
/** /**
* Lock update.
*
* Lock a file to see if an update is ongoing. * Lock a file to see if an update is ongoing.
* *
* @return bool The lock success * @return bool True if file is locked
*/ */
public function lockUpdate(): bool public function lockUpdate(): bool
{ {
try { try {
# Need flock function
if (!function_exists('flock')) {
throw new Exception("Can't call php function named flock");
}
# Cache writable ? # Cache writable ?
if (!is_writable(DC_TPL_CACHE)) { if (!is_writable(DC_TPL_CACHE)) {
throw new Exception("Can't write in cache fodler"); throw new Exception("Can't write in cache fodler");
} }
# Set file path # Set file path
$f_md5 = md5((string) dcCore::app()->blog?->id); $f_md5 = md5((string) dcCore::app()->blog?->id);
$cached_file = sprintf( $file = sprintf(
'%s/%s/%s/%s/%s.txt', '%s/%s/%s/%s/%s.txt',
DC_TPL_CACHE, DC_TPL_CACHE,
My::CACHE_DIR_NAME, My::id(),
substr($f_md5, 0, 2), substr($f_md5, 0, 2),
substr($f_md5, 2, 2), substr($f_md5, 2, 2),
$f_md5 $f_md5
); );
# Real path
$cached_file = (string) Path::real($cached_file, false); $file = Lock::lock($file);
// make dir if (is_null($file) || empty($file)) {
if (!is_dir(dirname($cached_file))) {
Files::makeDir(dirname($cached_file), true);
}
//ake file
if (!file_exists($cached_file)) {
!$fp = @fopen($cached_file, 'w');
if ($fp === false) {
throw new Exception("Can't create file");
}
fwrite($fp, '1', strlen('1'));
fclose($fp);
}
// open file
if (!($fp = @fopen($cached_file, 'r+'))) {
throw new Exception("Can't open file");
}
// lock file
if (!flock($fp, LOCK_EX | LOCK_NB)) {
//throw new Exception("Can't lock file");
return false; return false;
} }
$this->lock = $fp; self::$lock = $file;
return true; return true;
} catch (Exception $e) { } catch (Exception $e) {
// what ?
throw $e; throw $e;
} }
} }
/** /**
* Unlock update. * Unlock file of update process.
*/ */
public function unlockUpdate(): void public function unlockUpdate(): void
{ {
if ($this->lock) { if (!is_null(self::$lock)) {
@fclose($this->lock); Lock::unlock(self::$lock);
$this->lock = null; self::$lock = null;
} }
} }

148
src/Lock.php Normal file
View File

@ -0,0 +1,148 @@
<?php
/**
* @brief activityReport, a plugin for Dotclear 2
*
* @package Dotclear
* @subpackage Plugin
*
* @author Jean-Christian Denis and contributors
*
* @copyright Jean-Christian Denis
* @copyright GPL-2.0 https://www.gnu.org/licenses/gpl-2.0.html
*/
declare(strict_types=1);
namespace Dotclear\Plugin\activityReport;
use Dotclear\Helper\File\{
Files,
Path
};
class Lock
{
/**
* Locked files resource stack.
*
* @var array<string,resource>
*/
protected static $lock_stack = [];
/**
* Locked files status stack.
*
* @var array<string,bool>
*/
protected static $lock_disposable = [];
/**
* Last lock attempt error
*
* @var string
*/
protected static $lock_error = '';
/**
* Lock file.
*
* @param string $file The file path
* @param bool $disposable File only use to lock
*
* @return null|string Clean file path on success, empty string on error, null if already locked
*/
public static function lock(string $file, bool $disposable = false): ?string
{
# Real path
$file = Path::real($file, false);
if (false === $file) {
self::$lock_error = __("Can't get file path");
return '';
}
# not a dir
if (is_dir($file)) {
self::$lock_error = __("Can't lock a directory");
return '';
}
# already marked as locked
if (isset(self::$lock_stack[$file]) || $disposable && file_exists($file)) {
return null;
}
# Need flock function
if (!function_exists('flock')) {
self::$lock_error = __("Can't call php function named flock");
return '';
}
# Make dir
if (!is_dir(dirname($file))) {
Files::makeDir(dirname($file), true);
}
# Open new file
if (!file_exists($file)) {
$resource = @fopen($file, 'w');
if ($resource === false) {
self::$lock_error = __("Can't create file");
return '';
}
fwrite($resource, '1', strlen('1'));
//fclose($resource);
} else {
# Open existsing file
$resource = @fopen($file, 'r+');
if ($resource === false) {
self::$lock_error = __("Can't open file");
return '';
}
}
# Lock file
if (!flock($resource, LOCK_EX | LOCK_NB)) {
self::$lock_error = __("Can't lock file");
return '';
}
self::$lock_stack[$file] = $resource;
self::$lock_disposable[$file] = $disposable;
return $file;
}
/**
* Unlock file.
*
* @param string $file The file to unlock
*/
public static function unlock(string $file): void
{
if (isset(self::$lock_stack[$file])) {
fclose(self::$lock_stack[$file]);
if (!empty(self::$lock_disposable[$file]) && file_exists($file)) {
@unlink($file);
}
unset(
self::$lock_stack[$file],
self::$lock_disposable[$file]
);
}
}
/**
* Get last error from lock method.
*
* @return string The last lock error
*/
public static function getlastLockError(): string
{
return self::$lock_error;
}
}

View File

@ -24,9 +24,6 @@ class My
/** @var string Activity database table name */ /** @var string Activity database table name */
public const ACTIVITY_TABLE_NAME = 'activity'; public const ACTIVITY_TABLE_NAME = 'activity';
/** @var string Cache sub directory name */
public const CACHE_DIR_NAME = 'activityreport';
/** @var int Incremental version by breaking changes */ /** @var int Incremental version by breaking changes */
public const COMPATIBILITY_VERSION = 3; public const COMPATIBILITY_VERSION = 3;