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