début implémentation cache
This commit is contained in:
parent
4c91327bac
commit
ec0c0eef3e
7
bin/nucache.php
Executable file
7
bin/nucache.php
Executable file
@ -0,0 +1,7 @@
|
|||||||
|
#!/usr/bin/php
|
||||||
|
<?php
|
||||||
|
require $_composer_autoload_path?? __DIR__.'/../vendor/autoload.php';
|
||||||
|
|
||||||
|
use nulib\tools\NucacheApp;
|
||||||
|
|
||||||
|
NucacheApp::run();
|
49
src/file/cache/CacheData.php
vendored
Normal file
49
src/file/cache/CacheData.php
vendored
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
<?php
|
||||||
|
namespace nulib\file\cache;
|
||||||
|
|
||||||
|
use nulib\cl;
|
||||||
|
use nulib\php\func;
|
||||||
|
use Traversable;
|
||||||
|
|
||||||
|
class CacheData {
|
||||||
|
/** @var string identifiant de cette donnée */
|
||||||
|
const NAME = null;
|
||||||
|
|
||||||
|
/** @var callable une fonction permettant de calculer la donnée */
|
||||||
|
const COMPUTE = null;
|
||||||
|
|
||||||
|
function __construct(?array $params=null) {
|
||||||
|
$this->name = $params["name"] ?? static::NAME;
|
||||||
|
$this->name ??= bin2hex(random_bytes(8));
|
||||||
|
$this->compute = func::withn($params["compute"] ?? static::COMPUTE);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected string $name;
|
||||||
|
|
||||||
|
protected ?func $compute;
|
||||||
|
|
||||||
|
protected function compute() {
|
||||||
|
$compute = $this->compute;
|
||||||
|
return $compute !== null? $compute->invoke(): null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** obtenir la donnée, en l'itérant au préalable si elle est traversable */
|
||||||
|
function get(?string &$name, $compute=null) {
|
||||||
|
$name = $this->name;
|
||||||
|
$this->compute ??= func::withn($compute);
|
||||||
|
$data = $this->compute();
|
||||||
|
if ($data instanceof Traversable) {
|
||||||
|
$data = cl::all($data);
|
||||||
|
}
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** obtenir un itérateur sur la donnée ou null s'il n'y a pas de données */
|
||||||
|
function all(?string &$name, $compute=null): ?iterable {
|
||||||
|
$name = $this->name;
|
||||||
|
$this->compute ??= func::withn($compute);
|
||||||
|
$data = $this->compute();
|
||||||
|
if ($data !== null && !is_iterable($data)) $data = [$data];
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
}
|
178
src/file/cache/CacheFile.php
vendored
Normal file
178
src/file/cache/CacheFile.php
vendored
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
<?php
|
||||||
|
namespace nulib\file\cache;
|
||||||
|
|
||||||
|
use nulib\file\SharedFile;
|
||||||
|
use nulib\os\path;
|
||||||
|
use nulib\php\time\DateTime;
|
||||||
|
use nulib\php\time\Delay;
|
||||||
|
use nulib\str;
|
||||||
|
|
||||||
|
class CacheFile extends SharedFile {
|
||||||
|
/** @var string|int durée de vie par défaut des données mises en cache */
|
||||||
|
const DURATION = "1D"; // jusqu'au lendemain
|
||||||
|
|
||||||
|
function __construct($file, ?array $params=null) {
|
||||||
|
if ($file === null) {
|
||||||
|
$rand = bin2hex(random_bytes(8));
|
||||||
|
$file = sys_get_temp_dir()."/$rand";
|
||||||
|
}
|
||||||
|
$file = path::ensure_ext($file, ".metadata.cache", ".cache");
|
||||||
|
$this->basedir = path::dirname($file);
|
||||||
|
$basename = path::filename($file);
|
||||||
|
$this->basename = str::without_suffix(path::ext($basename), $basename);
|
||||||
|
$this->duration = Delay::with($params["duration"] ?? static::DURATION);
|
||||||
|
$this->overrideDuration = $params["override_duration"] ?? false;
|
||||||
|
$this->cacheNull = $params["cache_null"] ?? false;
|
||||||
|
$this->datafiles = [];
|
||||||
|
parent::__construct($file);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var string répertoire de base des fichiers de cache */
|
||||||
|
protected string $basedir;
|
||||||
|
|
||||||
|
/** @var string nom de base des fichiers de cache */
|
||||||
|
protected string $basename;
|
||||||
|
|
||||||
|
protected Delay $duration;
|
||||||
|
|
||||||
|
protected bool $overrideDuration;
|
||||||
|
|
||||||
|
protected bool $cacheNull;
|
||||||
|
|
||||||
|
protected array $datafiles;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* vérifier si le fichier est valide. s'il est invalide, il faut le recréer.
|
||||||
|
*
|
||||||
|
* on assume que le fichier existe, vu qu'il a été ouvert en c+b
|
||||||
|
*/
|
||||||
|
protected function isValid(): bool {
|
||||||
|
# considèrer que le fichier est invalide s'il est de taille nulle
|
||||||
|
return $this->getSize() > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** charger les données. le fichier a déjà été verrouillé en lecture */
|
||||||
|
protected function loadData(): ?array {
|
||||||
|
$this->rewind();
|
||||||
|
[
|
||||||
|
"start" => $start,
|
||||||
|
"duration" => $duration,
|
||||||
|
"datafiles" => $datafiles,
|
||||||
|
] = $this->unserialize(null, false, true);
|
||||||
|
if ($this->overrideDuration) {
|
||||||
|
$duration = Delay::with($this->duration, $start);
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
"start" => $start,
|
||||||
|
"duration" => $duration,
|
||||||
|
"datafiles" => $datafiles,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** tester s'il faut mettre les données à jour. le fichier a déjà été verrouillé en lecture */
|
||||||
|
protected function shouldUpdate(bool $noCache=false): bool {
|
||||||
|
if ($this->isValid()) {
|
||||||
|
/** @var Delay $duration */
|
||||||
|
["duration" => $duration,
|
||||||
|
] = $this->loadData();
|
||||||
|
$expired = $duration->isElapsed();
|
||||||
|
} else {
|
||||||
|
$expired = false;
|
||||||
|
$noCache = true;
|
||||||
|
}
|
||||||
|
return $noCache || $expired;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** sauvegarder les données. le fichier a déjà été verrouillé en écriture */
|
||||||
|
protected function saveData(?DateTime $start=null, ?Delay $duration=null): void {
|
||||||
|
$duration ??= $this->duration;
|
||||||
|
if ($start === null) {
|
||||||
|
$start = new DateTime();
|
||||||
|
$duration = Delay::with($duration, $start);
|
||||||
|
}
|
||||||
|
$this->ftruncate();
|
||||||
|
$this->serialize([
|
||||||
|
"start" => $start,
|
||||||
|
"duration" => $duration,
|
||||||
|
"datafiles" => $this->datafiles,
|
||||||
|
], false, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** tester si $value peut être mis en cache */
|
||||||
|
function shouldCache($value): bool {
|
||||||
|
return $this->cacheNull || $value !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** obtenir les informations sur le fichier */
|
||||||
|
function getInfos(): array {
|
||||||
|
$this->lockRead();
|
||||||
|
try {
|
||||||
|
if (!$this->isValid()) {
|
||||||
|
return ["valid" => false];
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @var DateTime $start
|
||||||
|
* @var Delay $duration
|
||||||
|
*/
|
||||||
|
[
|
||||||
|
"start" => $start,
|
||||||
|
"duration" => $duration,
|
||||||
|
"datafiles" => $datafiles,
|
||||||
|
] = $this->loadData();
|
||||||
|
return [
|
||||||
|
"valid" => true,
|
||||||
|
"size" => $this->getSize(),
|
||||||
|
"start" => $start,
|
||||||
|
"duration" => strval($duration),
|
||||||
|
"date_start" => $start->format(),
|
||||||
|
"date_end" => $duration->getDest()->format(),
|
||||||
|
"datafiles" => $datafiles,
|
||||||
|
];
|
||||||
|
} finally {
|
||||||
|
$this->unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const UPDATE_SUB = -1, UPDATE_SET = 0, UPDATE_ADD = 1;
|
||||||
|
|
||||||
|
/** mettre à jour la durée de validité du fichier */
|
||||||
|
function updateDuration($nduration, int $action=1): void {
|
||||||
|
$this->lockRead();
|
||||||
|
try {
|
||||||
|
if (!$this->isValid()) return;
|
||||||
|
$this->lockWrite();
|
||||||
|
/**
|
||||||
|
* @var DateTime $tstart
|
||||||
|
* @var Delay $duration
|
||||||
|
*/
|
||||||
|
[
|
||||||
|
"start" => $start,
|
||||||
|
"duration" => $duration,
|
||||||
|
] = $this->loadData();
|
||||||
|
if ($action < 0) $duration->subDuration($nduration);
|
||||||
|
elseif ($action > 0) $duration->addDuration($nduration);
|
||||||
|
$this->saveData($start, $duration);
|
||||||
|
} finally {
|
||||||
|
$this->unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** supprimer les fichiers s'ils ont expiré */
|
||||||
|
function deleteExpired(bool $force=false): bool {
|
||||||
|
$this->lockRead();
|
||||||
|
try {
|
||||||
|
if ($force || $this->shouldUpdate()) {
|
||||||
|
$this->lockWrite();
|
||||||
|
@unlink($this->file);
|
||||||
|
$basedir = $this->basedir;
|
||||||
|
foreach ($this->datafiles as $datafile) {
|
||||||
|
@unlink(path::join($basedir, $datafile));
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
$this->unlock();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
119
src/tools/NucacheApp.php
Normal file
119
src/tools/NucacheApp.php
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
<?php
|
||||||
|
namespace nulib\tools;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
use nulib\app\cli\Application;
|
||||||
|
use nulib\ext\yaml;
|
||||||
|
use nulib\file\cache\CacheFile;
|
||||||
|
use nulib\os\path;
|
||||||
|
use nulib\output\msg;
|
||||||
|
|
||||||
|
class NucacheApp extends Application {
|
||||||
|
const ACTION_READ = 10, ACTION_INFOS = 20, ACTION_CLEAN = 30;
|
||||||
|
const ACTION_UPDATE = 40, ACTION_UPDATE_ADD = 41, ACTION_UPDATE_SUB = 42, ACTION_UPDATE_SET = 43;
|
||||||
|
|
||||||
|
const ARGS = [
|
||||||
|
"merge" => parent::ARGS,
|
||||||
|
"purpose" => "gestion de fichiers cache",
|
||||||
|
["-r", "--read", "name" => "action", "value" => self::ACTION_READ,
|
||||||
|
"help" => "Afficher le contenu d'un fichier cache",
|
||||||
|
],
|
||||||
|
["-i", "--infos", "name" => "action", "value" => self::ACTION_INFOS,
|
||||||
|
"help" => "Afficher des informations sur le fichier cache",
|
||||||
|
],
|
||||||
|
["-k", "--clean", "name" => "action", "value" => self::ACTION_CLEAN,
|
||||||
|
"help" => "Supprimer le fichier cache s'il a expiré",
|
||||||
|
],
|
||||||
|
["-a", "--add-duration", "args" => 1,
|
||||||
|
"action" => [null, "->setActionUpdate", self::ACTION_UPDATE_ADD],
|
||||||
|
"help" => "Ajouter le nombre de secondes spécifié à la durée du cache",
|
||||||
|
],
|
||||||
|
["-b", "--sub-duration", "args" => 1,
|
||||||
|
"action" => [null, "->setActionUpdate", self::ACTION_UPDATE_SUB],
|
||||||
|
"help" => "Enlever le nombre de secondes spécifié à la durée du cache",
|
||||||
|
],
|
||||||
|
["-s", "--set-duration", "args" => 1,
|
||||||
|
"action" => [null, "->setActionUpdate", self::ACTION_UPDATE_SET],
|
||||||
|
"help" => "Mettre à jour la durée du cache à la valeur spécifiée",
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $action = self::ACTION_READ;
|
||||||
|
|
||||||
|
protected $updateAction, $updateDuration;
|
||||||
|
|
||||||
|
protected $args;
|
||||||
|
|
||||||
|
function setActionUpdate(int $action, $updateDuration): void {
|
||||||
|
$this->action = self::ACTION_UPDATE;
|
||||||
|
switch ($action) {
|
||||||
|
case self::ACTION_UPDATE_SUB: $this->updateAction = CacheFile::UPDATE_SUB; break;
|
||||||
|
case self::ACTION_UPDATE_SET: $this->updateAction = CacheFile::UPDATE_SET; break;
|
||||||
|
case self::ACTION_UPDATE_ADD: $this->updateAction = CacheFile::UPDATE_ADD; break;
|
||||||
|
}
|
||||||
|
$this->updateDuration = $updateDuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function findCaches(string $dir, ?array &$files): void {
|
||||||
|
foreach (glob("$dir/*") as $file) {
|
||||||
|
if (is_dir($file)) {
|
||||||
|
$this->findCaches($file, $files);
|
||||||
|
} elseif (is_file($file) && fnmatch("*.cache", $file)) {
|
||||||
|
$files[] = $file;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function main() {
|
||||||
|
$files = [];
|
||||||
|
foreach ($this->args as $arg) {
|
||||||
|
if (is_dir($arg)) {
|
||||||
|
$this->findCaches($arg, $files);
|
||||||
|
} elseif (is_file($arg)) {
|
||||||
|
$files[] = $arg;
|
||||||
|
} else {
|
||||||
|
msg::warning("$arg: fichier invalide ou introuvable");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$showSection = count($files) > 1;
|
||||||
|
foreach ($files as $file) {
|
||||||
|
switch ($this->action) {
|
||||||
|
case self::ACTION_READ:
|
||||||
|
if ($showSection) msg::section($file);
|
||||||
|
$cache = new CacheFile($file, [
|
||||||
|
"duration" => "INF",
|
||||||
|
"override_duration" => true,
|
||||||
|
]);
|
||||||
|
//yaml::dump($cache->get());
|
||||||
|
break;
|
||||||
|
case self::ACTION_INFOS:
|
||||||
|
if ($showSection) msg::section($file);
|
||||||
|
$cache = new CacheFile($file);
|
||||||
|
yaml::dump($cache->getInfos());
|
||||||
|
break;
|
||||||
|
case self::ACTION_CLEAN:
|
||||||
|
msg::action(path::ppath($file));
|
||||||
|
$cache = new CacheFile($file);
|
||||||
|
try {
|
||||||
|
if ($cache->deleteExpired()) msg::asuccess("fichier supprimé");
|
||||||
|
else msg::astep("fichier non expiré");
|
||||||
|
} catch (Exception $e) {
|
||||||
|
msg::afailure($e);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case self::ACTION_UPDATE:
|
||||||
|
msg::action(path::ppath($file));
|
||||||
|
$cache = new CacheFile($file);
|
||||||
|
try {
|
||||||
|
$cache->updateDuration($this->updateDuration, $this->updateAction);
|
||||||
|
msg::asuccess("fichier mis à jour");
|
||||||
|
} catch (Exception $e) {
|
||||||
|
msg::afailure($e);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
self::die("$this->action: action non implémentée");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user