diff --git a/src/Lock.php b/src/Lock.php new file mode 100644 index 0000000..53de1c9 --- /dev/null +++ b/src/Lock.php @@ -0,0 +1,148 @@ + + */ + protected static $lock_stack = []; + + /** + * Locked files status stack. + * + * @var array + */ + 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; + } +} diff --git a/src/ZoneclearFeedServer.php b/src/ZoneclearFeedServer.php index e45b5bb..4517a0c 100644 --- a/src/ZoneclearFeedServer.php +++ b/src/ZoneclearFeedServer.php @@ -63,7 +63,7 @@ class ZoneclearFeedServer /** @var Settings The settings instance */ public readonly Settings $settings; - /** @var null|resource $lock File lock for update */ + /** @var null|string $lock File lock for update */ private static $lock = null; /** @var null|string $user Affiliate user ID */ @@ -416,54 +416,30 @@ class ZoneclearFeedServer * * @return bool True if file is locked */ - public function lockUpdate() + public function lockUpdate(): bool { try { - # Need flock function - if (!function_exists('flock')) { - throw new Exception("Can't call php function named flock"); - } # Cache writable ? if (!is_writable(DC_TPL_CACHE)) { throw new Exception("Can't write in cache fodler"); } # Set file path - $f_md5 = md5((string) dcCore::app()->blog?->id); - $cached_file = sprintf( + $f_md5 = md5((string) dcCore::app()->blog?->id); + $file = sprintf( '%s/%s/%s/%s/%s.txt', DC_TPL_CACHE, - 'periodical', + My::id(), substr($f_md5, 0, 2), substr($f_md5, 2, 2), $f_md5 ); - # Real path - $cached_file = Path::real($cached_file, false); - if (false === $cached_file) { - throw new Exception("Can't get cache file path"); + + $file = Lock::lock($file); + if (is_null($file) || empty($file)) { + return false; } - # Make dir - if (!is_dir(dirname($cached_file))) { - Files::makeDir(dirname($cached_file), true); - } - # Make 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)) { - throw new Exception("Can't lock file"); - } - self::$lock = $fp; + + self::$lock = $file; return true; } catch (Exception $e) { @@ -477,7 +453,7 @@ class ZoneclearFeedServer public function unlockUpdate(): void { if (!is_null(self::$lock)) { - @fclose(self::$lock); + Lock::unlock(self::$lock); self::$lock = null; } } @@ -500,13 +476,7 @@ class ZoneclearFeedServer } # Limit to one update at a time - try { - $this->lockUpdate(); - } catch (Exception $e) { - if ($throw) { - throw $e; - } - + if (!$this->lockUpdate()) { return false; }