<pman>Intégration de la branche dev74
This commit is contained in:
commit
b56373c552
@ -39,8 +39,11 @@ pci "maj projet"
|
|||||||
|
|
||||||
prel -C
|
prel -C
|
||||||
|
|
||||||
commit="$(git log --grep="Init changelog . version ${version}p82" --format=%H)"
|
commit="$(git log --grep="Init changelog . version ${version}p82" --format=%H)" &&
|
||||||
|
echo "commit=$commit"
|
||||||
|
|
||||||
git checkout dev74
|
git checkout dev74
|
||||||
|
|
||||||
git cherry-pick "$commit"
|
git cherry-pick "$commit"
|
||||||
pp -a
|
pp -a
|
||||||
~~~
|
~~~
|
||||||
|
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();
|
@ -90,8 +90,8 @@
|
|||||||
"nur_bin/ldap-delete.php",
|
"nur_bin/ldap-delete.php",
|
||||||
"nur_bin/ldap-get-infos.php",
|
"nur_bin/ldap-get-infos.php",
|
||||||
"nur_bin/ldap-search.php",
|
"nur_bin/ldap-search.php",
|
||||||
"nur_bin/sqlite-storage.php",
|
"nur_bin/storage.sqlite.php",
|
||||||
"nur_bin/mysql-storage.php"
|
"nur_bin/storage.mysql.php"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"uc": "@php sbin/update_classes.php"
|
"uc": "@php sbin/update_classes.php"
|
||||||
|
116
src/cache/CacheChannel.php
vendored
Normal file
116
src/cache/CacheChannel.php
vendored
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
<?php
|
||||||
|
namespace nulib\cache;
|
||||||
|
|
||||||
|
use nulib\cl;
|
||||||
|
use nulib\db\CapacitorChannel;
|
||||||
|
use nulib\php\time\DateTime;
|
||||||
|
use nulib\php\time\Delay;
|
||||||
|
|
||||||
|
class CacheChannel extends CapacitorChannel {
|
||||||
|
/** @var int durée de vie par défaut du cache */
|
||||||
|
const DURATION = "1D"; // jusqu'au lendemain
|
||||||
|
|
||||||
|
const INCLUDES = null;
|
||||||
|
|
||||||
|
const EXCLUDES = null;
|
||||||
|
|
||||||
|
const COLUMN_DEFINITIONS = [
|
||||||
|
"group_id" => "varchar(64) not null",
|
||||||
|
"id" => "varchar(64) not null",
|
||||||
|
"date_start" => "datetime",
|
||||||
|
"duration_" => "text",
|
||||||
|
"primary key (group_id, id)",
|
||||||
|
];
|
||||||
|
|
||||||
|
static function get_cache_ids($id): array {
|
||||||
|
if (is_array($id)) {
|
||||||
|
$keys = array_keys($id);
|
||||||
|
if (array_key_exists("group_id", $id)) $groupIdKey = "group_id";
|
||||||
|
else $groupIdKey = $keys[1] ?? null;
|
||||||
|
$groupId = $id[$groupIdKey] ?? "";
|
||||||
|
if (array_key_exists("id", $id)) $idKey = "id";
|
||||||
|
else $idKey = $keys[0] ?? null;
|
||||||
|
$id = $id[$idKey] ?? "";
|
||||||
|
} else {
|
||||||
|
$groupId = "";
|
||||||
|
}
|
||||||
|
if (preg_match('/^(.*\\\\)?([^\\\\]+)$/', $groupId, $ms)) {
|
||||||
|
# si le groupe est une classe, faire un hash du package pour limiter la
|
||||||
|
# longueur du groupe
|
||||||
|
[$package, $groupId] = [$ms[1], $ms[2]];
|
||||||
|
$package = substr(md5($package), 0, 4);
|
||||||
|
$groupId = "${groupId}_$package";
|
||||||
|
}
|
||||||
|
return ["group_id" => $groupId, "id" => $id];
|
||||||
|
}
|
||||||
|
|
||||||
|
function __construct(?string $duration=null, ?string $name=null) {
|
||||||
|
parent::__construct($name);
|
||||||
|
$this->duration = $duration ?? static::DURATION;
|
||||||
|
$this->includes = static::INCLUDES;
|
||||||
|
$this->excludes = static::EXCLUDES;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected string $duration;
|
||||||
|
|
||||||
|
protected ?array $includes;
|
||||||
|
|
||||||
|
protected ?array $excludes;
|
||||||
|
|
||||||
|
function getItemValues($item): ?array {
|
||||||
|
return cl::merge(self::get_cache_ids($item), [
|
||||||
|
"item" => null,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onCreate($item, array $values, ?array $alwaysNull, ?string $duration=null): ?array {
|
||||||
|
$now = new DateTime();
|
||||||
|
$duration ??= $this->duration;
|
||||||
|
return [
|
||||||
|
"date_start" => $now,
|
||||||
|
"duration" => new Delay($duration, $now),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
function onUpdate($item, array $values, array $pvalues, ?string $duration=null): ?array {
|
||||||
|
$now = new DateTime();
|
||||||
|
$duration ??= $this->duration;
|
||||||
|
return [
|
||||||
|
"date_start" => $now,
|
||||||
|
"duration" => new Delay($duration, $now),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
function shouldUpdate($id, bool $noCache=false): bool {
|
||||||
|
if ($noCache) return true;
|
||||||
|
|
||||||
|
$cacheIds = self::get_cache_ids($id);
|
||||||
|
$groupId = $cacheIds["group_id"];
|
||||||
|
if ($groupId) {
|
||||||
|
$includes = $this->includes;
|
||||||
|
$shouldInclude = $includes !== null && in_array($groupId, $includes);
|
||||||
|
$excludes = $this->excludes;
|
||||||
|
$shouldExclude = $excludes !== null && in_array($groupId, $excludes);
|
||||||
|
if (!$shouldInclude || $shouldExclude) return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$found = false;
|
||||||
|
$expired = false;
|
||||||
|
$this->each($cacheIds,
|
||||||
|
function($item, $values) use (&$found, &$expired) {
|
||||||
|
$found = true;
|
||||||
|
$expired = $values["duration"]->isElapsed();
|
||||||
|
});
|
||||||
|
return !$found || $expired;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setCached($id, ?string $duration=null): void {
|
||||||
|
$cacheIds = self::get_cache_ids($id);
|
||||||
|
$this->charge($cacheIds, null, [$duration]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetCached($id) {
|
||||||
|
$cacheIds = self::get_cache_ids($id);
|
||||||
|
$this->delete($cacheIds);
|
||||||
|
}
|
||||||
|
}
|
50
src/cache/CacheData.php
vendored
Normal file
50
src/cache/CacheData.php
vendored
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
<?php
|
||||||
|
namespace nulib\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($compute=null, ?string $name=null) {
|
||||||
|
$this->name = $name ?? static::NAME ?? "";
|
||||||
|
$this->compute = func::withn($compute ?? static::COMPUTE);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected string $name;
|
||||||
|
|
||||||
|
protected ?func $compute;
|
||||||
|
|
||||||
|
protected function compute() {
|
||||||
|
$compute = $this->compute;
|
||||||
|
return $compute !== null? $compute->invoke(): null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getName() : string {
|
||||||
|
return $this->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** obtenir la donnée, en l'itérant au préalable si elle est traversable */
|
||||||
|
function get($compute=null) {
|
||||||
|
$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($compute=null): ?iterable {
|
||||||
|
$this->compute ??= func::withn($compute);
|
||||||
|
$data = $this->compute();
|
||||||
|
if ($data !== null && !is_iterable($data)) $data = [$data];
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
}
|
53
src/cache/CacheDataChannel.php
vendored
Normal file
53
src/cache/CacheDataChannel.php
vendored
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
<?php
|
||||||
|
namespace nulib\cache;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use IteratorAggregate;
|
||||||
|
use nulib\cache\CacheChannel;
|
||||||
|
use nulib\cache\storage_cache;
|
||||||
|
use nulib\cl;
|
||||||
|
use nulib\db\CapacitorChannel;
|
||||||
|
use Traversable;
|
||||||
|
|
||||||
|
class CacheDataChannel extends CapacitorChannel implements IteratorAggregate {
|
||||||
|
const COLUMN_DEFINITIONS = [
|
||||||
|
"key" => "varchar(128) primary key not null",
|
||||||
|
"all_values" => "mediumtext",
|
||||||
|
];
|
||||||
|
|
||||||
|
function __construct($id, callable $builder, ?string $duration=null) {
|
||||||
|
$this->cacheIds = $cacheIds = CacheChannel::get_cache_ids($id);
|
||||||
|
$this->builder = Closure::fromCallable($builder);
|
||||||
|
$this->duration = $duration;
|
||||||
|
$name = "{$cacheIds["group_id"]}-{$cacheIds["id"]}";
|
||||||
|
parent::__construct($name);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected array $cacheIds;
|
||||||
|
|
||||||
|
protected Closure $builder;
|
||||||
|
|
||||||
|
protected ?string $duration = null;
|
||||||
|
|
||||||
|
function getItemValues($item): ?array {
|
||||||
|
$key = array_keys($item)[0];
|
||||||
|
$row = $item[$key];
|
||||||
|
return [
|
||||||
|
"key" => $key,
|
||||||
|
"item" => $row,
|
||||||
|
"all_values" => implode(" ", cl::filter_n(cl::with($row))),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
function getIterator(): Traversable {
|
||||||
|
$cm = storage_cache::get();
|
||||||
|
if ($cm->shouldUpdate($this->cacheIds)) {
|
||||||
|
$this->capacitor->reset();
|
||||||
|
foreach (($this->builder)() as $key => $row) {
|
||||||
|
$this->charge([$key => $row]);
|
||||||
|
}
|
||||||
|
$cm->setCached($this->cacheIds, $this->duration);
|
||||||
|
}
|
||||||
|
return $this->discharge(false);
|
||||||
|
}
|
||||||
|
}
|
368
src/cache/CacheFile.php
vendored
Normal file
368
src/cache/CacheFile.php
vendored
Normal file
@ -0,0 +1,368 @@
|
|||||||
|
<?php
|
||||||
|
namespace nulib\cache;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
use nulib\cv;
|
||||||
|
use nulib\file;
|
||||||
|
use nulib\file\SharedFile;
|
||||||
|
use nulib\os\path;
|
||||||
|
use nulib\php\func;
|
||||||
|
use nulib\php\time\DateTime;
|
||||||
|
use nulib\php\time\Delay;
|
||||||
|
use nulib\str;
|
||||||
|
use nulib\ValueException;
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
const EXT = ".cache";
|
||||||
|
|
||||||
|
protected function ensure_source($source): CacheData {
|
||||||
|
if ($source instanceof CacheData) return $source;
|
||||||
|
if (cv::subclass_of($source, CacheData::class)) return new $source();
|
||||||
|
if (func::is_callable($source)) return new CacheData($source);
|
||||||
|
throw ValueException::invalid_type($source, CacheData::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
function __construct($file, ?array $params=null) {
|
||||||
|
if ($file === null) {
|
||||||
|
$rand = bin2hex(random_bytes(8));
|
||||||
|
$file = path::join(sys_get_temp_dir(), $rand);
|
||||||
|
}
|
||||||
|
$file = path::ensure_ext($file, self::EXT);
|
||||||
|
$this->basedir = path::dirname($file);
|
||||||
|
$basename = path::filename($file);
|
||||||
|
$this->basename = str::without_suffix(self::EXT, $basename);
|
||||||
|
$this->initialDuration = Delay::with($params["duration"] ?? static::DURATION);
|
||||||
|
$this->overrideDuration = $params["override_duration"] ?? false;
|
||||||
|
$this->readonly = $params["readonly"] ?? false;
|
||||||
|
$this->cacheNull = $params["cache_null"] ?? false;
|
||||||
|
$data = $params["data"] ?? null;
|
||||||
|
$this->sources = null;
|
||||||
|
if ($data === null) {
|
||||||
|
$this->source = null;
|
||||||
|
} elseif ($data instanceof CacheData) {
|
||||||
|
$this->source = $data;
|
||||||
|
} elseif (func::is_callable($data)) {
|
||||||
|
$this->source = new CacheData($data);
|
||||||
|
} elseif (!is_array($data)) {
|
||||||
|
$this->source = self::ensure_source($data);
|
||||||
|
} else {
|
||||||
|
$sources = [];
|
||||||
|
$index = 0;
|
||||||
|
foreach ($data as $key => $source) {
|
||||||
|
$source = self::ensure_source($source);
|
||||||
|
if ($key === $index) {
|
||||||
|
$index++;
|
||||||
|
$key = $source->getName();
|
||||||
|
}
|
||||||
|
$sources[$key] = $source;
|
||||||
|
}
|
||||||
|
$this->sources = $sources;
|
||||||
|
$this->source = null;
|
||||||
|
}
|
||||||
|
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 $initialDuration;
|
||||||
|
|
||||||
|
protected bool $overrideDuration;
|
||||||
|
|
||||||
|
protected bool $readonly;
|
||||||
|
|
||||||
|
protected bool $cacheNull;
|
||||||
|
|
||||||
|
protected ?array $sources;
|
||||||
|
|
||||||
|
protected ?CacheData $source;
|
||||||
|
|
||||||
|
protected function getSource($data): CacheData {
|
||||||
|
if ($data === null) {
|
||||||
|
return $this->source ??= new CacheData(function() {
|
||||||
|
return $this->compute();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
$source = $data;
|
||||||
|
if (is_string($source) || is_int($source)) {
|
||||||
|
$source = $this->sources[$source] ?? null;
|
||||||
|
if ($source === null) throw ValueException::invalid_key($data);
|
||||||
|
}
|
||||||
|
if ($source instanceof CacheData) return $source;
|
||||||
|
throw ValueException::invalid_type($data, CacheData::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
function isValid(): bool {
|
||||||
|
# considèrer que le fichier est invalide s'il est de taille nulle
|
||||||
|
return $this->getSize() > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ?DateTime $start;
|
||||||
|
|
||||||
|
protected ?Delay $duration;
|
||||||
|
|
||||||
|
protected ?array $datafilenames;
|
||||||
|
|
||||||
|
/** charger les données. le fichier a déjà été verrouillé en lecture */
|
||||||
|
protected function loadMetadata(): void {
|
||||||
|
if ($this->isValid()) {
|
||||||
|
$this->rewind();
|
||||||
|
[
|
||||||
|
"start" => $start,
|
||||||
|
"duration" => $duration,
|
||||||
|
"datafiles" => $datafilenames,
|
||||||
|
] = $this->unserialize(null, false, true);
|
||||||
|
if ($this->overrideDuration) {
|
||||||
|
$duration = Delay::with($this->initialDuration, $start);
|
||||||
|
}
|
||||||
|
$datafilenames = array_fill_keys($datafilenames, true);
|
||||||
|
} else {
|
||||||
|
$start = null;
|
||||||
|
$duration = null;
|
||||||
|
$datafilenames = [];
|
||||||
|
}
|
||||||
|
$this->start = $start;
|
||||||
|
$this->duration = $duration;
|
||||||
|
$this->datafilenames = $datafilenames;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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()) {
|
||||||
|
$expired = $this->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 saveMetadata(): void {
|
||||||
|
$this->duration ??= $this->initialDuration;
|
||||||
|
if ($this->start === null) {
|
||||||
|
$this->start = new DateTime();
|
||||||
|
$this->duration = Delay::with($this->duration, $this->start);
|
||||||
|
}
|
||||||
|
$datafilenames = array_keys($this->datafilenames);
|
||||||
|
$this->ftruncate();
|
||||||
|
$this->serialize([
|
||||||
|
"start" => $this->start,
|
||||||
|
"duration" => $this->duration,
|
||||||
|
"datafiles" => $datafilenames,
|
||||||
|
], false, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function loadData(string $datafile) {
|
||||||
|
return file::reader($datafile)->unserialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function saveData(string $datafile, $data): void {
|
||||||
|
file::writer($datafile)->serialize($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function unlinkDatafile(string $datafilename): void {
|
||||||
|
$datafile = path::join($this->basedir, $datafilename);
|
||||||
|
@unlink($datafile);
|
||||||
|
unset($this->datafilenames[$datafilename]);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function unlinkFiles(bool $datafilesOnly=false): void {
|
||||||
|
if (!$datafilesOnly) @unlink($this->file);
|
||||||
|
foreach ($this->datafilenames as $datafilename) {
|
||||||
|
$this->unlinkDatafile($datafilename);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** tester si $value peut être mis en cache */
|
||||||
|
protected function shouldCache($value): bool {
|
||||||
|
return $this->cacheNull || $value !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ?DateTime $ostart;
|
||||||
|
|
||||||
|
protected ?Delay $oduration;
|
||||||
|
|
||||||
|
protected ?array $odatafilenames;
|
||||||
|
|
||||||
|
protected function beforeAction() {
|
||||||
|
$this->loadMetadata();
|
||||||
|
$this->ostart = cv::clone($this->start);
|
||||||
|
$this->oduration = cv::clone($this->duration);
|
||||||
|
$this->odatafilenames = cv::clone($this->datafilenames);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function afterAction() {
|
||||||
|
$start = $this->start;
|
||||||
|
$duration = $this->duration;
|
||||||
|
$oduration = $this->oduration;
|
||||||
|
$datafilenames = $this->datafilenames;
|
||||||
|
$modified = false;
|
||||||
|
if ($start != $this->ostart) $modified = true;
|
||||||
|
if ($duration === null || $oduration === null) $modified = true;
|
||||||
|
elseif ($duration->getDest() != $oduration->getDest()) $modified = true;
|
||||||
|
# égalité stricte uniquement pour $datafiles qui est un array
|
||||||
|
if ($datafilenames !== $this->odatafilenames) $modified = true;
|
||||||
|
if ($modified && !$this->readonly) {
|
||||||
|
$this->lockWrite();
|
||||||
|
$this->saveMetadata();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function action(callable $callback, bool $willWrite=false) {
|
||||||
|
if ($willWrite && !$this->readonly) $this->lockWrite();
|
||||||
|
else $this->lockRead();
|
||||||
|
try {
|
||||||
|
$this->beforeAction();
|
||||||
|
$result = $callback();
|
||||||
|
$this->afterAction();
|
||||||
|
return $result;
|
||||||
|
} finally {
|
||||||
|
$this->start = null;
|
||||||
|
$this->duration = null;
|
||||||
|
$this->datafilenames = null;
|
||||||
|
$this->unlock(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function compute() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function get($data=null, bool $noCache=false) {
|
||||||
|
return $this->action(function () use ($data, $noCache) {
|
||||||
|
$source = $this->getSource($data);
|
||||||
|
$dataname = $source->getName();
|
||||||
|
$datafilename = ".{$this->basename}.{$dataname}".self::EXT;
|
||||||
|
$datafile = path::join($this->basedir, $datafilename);
|
||||||
|
|
||||||
|
$updateMetadata = $this->shouldUpdate($noCache);
|
||||||
|
$updateData = !array_key_exists($datafilename, $this->datafilenames);
|
||||||
|
if (!$this->readonly && ($updateMetadata || $updateData)) {
|
||||||
|
$this->lockWrite();
|
||||||
|
if ($updateMetadata) {
|
||||||
|
# il faut refaire tout le cache
|
||||||
|
$this->unlinkFiles(true);
|
||||||
|
$this->start = null;
|
||||||
|
$this->duration = null;
|
||||||
|
$updateData = true;
|
||||||
|
}
|
||||||
|
if ($updateData) {
|
||||||
|
# calculer un fichier
|
||||||
|
try {
|
||||||
|
$data = $source->get();
|
||||||
|
} catch (Exception $e) {
|
||||||
|
# ne pas garder le fichier en cas d'exception
|
||||||
|
$this->unlinkDatafile($datafile);
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
} elseif (file_exists($datafile)) {
|
||||||
|
$data = $this->loadData($datafile);
|
||||||
|
} else {
|
||||||
|
$data = null;
|
||||||
|
}
|
||||||
|
if ($this->shouldCache($data)) {
|
||||||
|
$this->saveData($datafile, $data);
|
||||||
|
$this->datafilenames[$datafilename] = true;
|
||||||
|
} else {
|
||||||
|
# ne pas garder le fichier s'il ne faut pas mettre en cache
|
||||||
|
$this->unlinkDatafile($datafile);
|
||||||
|
}
|
||||||
|
} elseif (file_exists($datafile)) {
|
||||||
|
$data = $this->loadData($datafile);
|
||||||
|
} else {
|
||||||
|
$data = null;
|
||||||
|
}
|
||||||
|
return $data;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function all($data=null, bool $noCache=false): ?iterable {
|
||||||
|
$data = $this->get($data, $noCache);
|
||||||
|
if ($data !== null && !is_iterable($data)) $data = [$data];
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
function delete($data=null): void {
|
||||||
|
$source = $this->getSource($data);
|
||||||
|
$dataname = $source->getName();
|
||||||
|
$datafilename = ".{$this->basename}.{$dataname}".self::EXT;
|
||||||
|
$this->unlinkDatafile($datafilename);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** obtenir les informations sur le fichier */
|
||||||
|
function getInfos(): array {
|
||||||
|
return $this->action(function () {
|
||||||
|
if (!$this->isValid()) {
|
||||||
|
return ["valid" => false];
|
||||||
|
}
|
||||||
|
$start = $this->start;
|
||||||
|
$duration = $this->duration;
|
||||||
|
$datafilenames = $this->datafilenames;
|
||||||
|
$datafiles = [];
|
||||||
|
foreach (array_keys($datafilenames) as $datafilename) {
|
||||||
|
$datafile = path::join($this->basedir, $datafilename);
|
||||||
|
$name = $datafilename;
|
||||||
|
str::del_prefix($name, ".{$this->basename}.");
|
||||||
|
str::del_suffix($name, self::EXT);
|
||||||
|
if (file_exists($datafile)) {
|
||||||
|
$size = filesize($datafile);
|
||||||
|
} else {
|
||||||
|
$size = null;
|
||||||
|
}
|
||||||
|
$datafiles[$name] = $size;
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
"valid" => true,
|
||||||
|
"start" => $start,
|
||||||
|
"duration" => strval($duration),
|
||||||
|
"date_start" => $start->format(),
|
||||||
|
"date_end" => $duration->getDest()->format(),
|
||||||
|
"datafiles" => $datafiles,
|
||||||
|
];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const UPDATE_SUB = -1, UPDATE_SET = 0, UPDATE_ADD = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* mettre à jour la durée de validité du fichier
|
||||||
|
*
|
||||||
|
* XXX UPDATE_SET n'est pas implémenté
|
||||||
|
*/
|
||||||
|
function updateDuration($nduration, int $action=self::UPDATE_ADD): void {
|
||||||
|
if ($this->readonly) return;
|
||||||
|
$this->action(function () use ($nduration, $action) {
|
||||||
|
if (!$this->isValid()) return;
|
||||||
|
$duration = $this->duration;
|
||||||
|
if ($action < 0) $duration->subDuration($nduration);
|
||||||
|
elseif ($action > 0) $duration->addDuration($nduration);
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** supprimer les fichiers s'ils ont expiré */
|
||||||
|
function deleteExpired(bool $force=false): bool {
|
||||||
|
if ($this->readonly) return false;
|
||||||
|
return $this->action(function () use ($force) {
|
||||||
|
if ($force || $this->shouldUpdate()) {
|
||||||
|
$this->unlinkFiles();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
}
|
39
src/cache/storage_cache.php
vendored
Normal file
39
src/cache/storage_cache.php
vendored
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
namespace nulib\cache;
|
||||||
|
|
||||||
|
use nulib\cache\CacheChannel;
|
||||||
|
use nulib\cache\CacheDataChannel;
|
||||||
|
use nulib\db\Capacitor;
|
||||||
|
use nulib\db\CapacitorStorage;
|
||||||
|
use nulib\db\sqlite\SqliteStorage;
|
||||||
|
|
||||||
|
class storage_cache {
|
||||||
|
protected static ?CapacitorStorage $storage = null;
|
||||||
|
|
||||||
|
static function set_storage(CapacitorStorage $storage): CapacitorStorage {
|
||||||
|
return self::$storage = $storage;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static function get_storage(): CapacitorStorage {
|
||||||
|
return self::$storage ??= new SqliteStorage("");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static ?CacheChannel $channel = null;
|
||||||
|
|
||||||
|
static function set(?CacheChannel $channel): CacheChannel {
|
||||||
|
$channel ??= new CacheChannel();
|
||||||
|
new Capacitor(self::get_storage(), $channel);
|
||||||
|
return self::$channel = $channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
static function get(): CacheChannel {
|
||||||
|
if (self::$channel !== null) return self::$channel;
|
||||||
|
else return self::set(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
static function new(?CacheDataChannel $channel, $id=null, ?callable $builder=null): CacheDataChannel {
|
||||||
|
$channel ??= new CacheDataChannel($id, $builder);
|
||||||
|
new Capacitor(self::get_storage(), $channel);
|
||||||
|
return $channel;
|
||||||
|
}
|
||||||
|
}
|
129
src/tools/NucacheApp.php
Normal file
129
src/tools/NucacheApp.php
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
<?php
|
||||||
|
namespace nulib\tools;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
use nulib\app\cli\Application;
|
||||||
|
use nulib\ext\yaml;
|
||||||
|
use nulib\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",
|
||||||
|
],
|
||||||
|
#XXX pas encore implémenté
|
||||||
|
//["-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 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, [
|
||||||
|
"readonly" => true,
|
||||||
|
"duration" => "INF",
|
||||||
|
"override_duration" => true,
|
||||||
|
]);
|
||||||
|
yaml::dump($cache->get());
|
||||||
|
break;
|
||||||
|
case self::ACTION_INFOS:
|
||||||
|
if ($showSection) msg::section($file);
|
||||||
|
$cache = new CacheFile($file, [
|
||||||
|
"readonly" => true,
|
||||||
|
]);
|
||||||
|
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::adone("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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
1
tbin/.gitignore
vendored
1
tbin/.gitignore
vendored
@ -1,2 +1,3 @@
|
|||||||
/output-forever.log
|
/output-forever.log
|
||||||
/devel/
|
/devel/
|
||||||
|
/*.cache
|
||||||
|
76
tbin/test-nucache.php
Executable file
76
tbin/test-nucache.php
Executable file
@ -0,0 +1,76 @@
|
|||||||
|
#!/usr/bin/php
|
||||||
|
<?php
|
||||||
|
require __DIR__.'/../vendor/autoload.php';
|
||||||
|
|
||||||
|
use nulib\cache\CacheData;
|
||||||
|
use nulib\cache\CacheFile;
|
||||||
|
use nulib\ext\yaml;
|
||||||
|
use nulib\os\sh;
|
||||||
|
|
||||||
|
function show(string $prefix, CacheFile $cache, bool $dumpInfos=true): void {
|
||||||
|
Txx("$prefix=", $cache->get());
|
||||||
|
if ($dumpInfos) {
|
||||||
|
yaml::dump($cache->getInfos());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//system("rm -f *.cache .*.cache");
|
||||||
|
|
||||||
|
$what = [
|
||||||
|
//"null",
|
||||||
|
"one",
|
||||||
|
//"two",
|
||||||
|
//"three",
|
||||||
|
];
|
||||||
|
$duration = 10;
|
||||||
|
|
||||||
|
if (in_array("null", $what)) {
|
||||||
|
$null = new CacheFile("null", [
|
||||||
|
"duration" => $duration,
|
||||||
|
]);
|
||||||
|
show("null", $null);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (in_array("one", $what)) {
|
||||||
|
$one = new class("one", [
|
||||||
|
"duration" => $duration,
|
||||||
|
]) extends CacheFile {
|
||||||
|
protected function compute() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
show("one", $one);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (in_array("two", $what)) {
|
||||||
|
$two = new CacheFile("two", [
|
||||||
|
"duration" => $duration,
|
||||||
|
"data" => new CacheData(function () {
|
||||||
|
return 2;
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
show("two", $two);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (in_array("three", $what)) {
|
||||||
|
$data31 = new CacheData(function () {
|
||||||
|
return 31;
|
||||||
|
}, "data31name");
|
||||||
|
|
||||||
|
$data32 = new CacheData(function () {
|
||||||
|
return 32;
|
||||||
|
});
|
||||||
|
|
||||||
|
$three = new CacheFile("three", [
|
||||||
|
"data" => [
|
||||||
|
"data31" => $data31,
|
||||||
|
$data31, # name=data31name
|
||||||
|
"data32" => $data32,
|
||||||
|
$data32, # name=""
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
Txx("three.0=", $three->get("data31"));
|
||||||
|
Txx("three.1=", $three->get("data31name"));
|
||||||
|
Txx("three.2=", $three->get("data32"));
|
||||||
|
Txx("three.3=", $three->get(""));
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user