<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