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