caching and cache time configuration
authorChristian Weiske <cweiske@cweiske.de>
Tue, 8 Apr 2014 05:49:02 +0000 (07:49 +0200)
committerChristian Weiske <cweiske@cweiske.de>
Tue, 8 Apr 2014 05:49:02 +0000 (07:49 +0200)
.gitignore
README.rst
src/phancap/Authenticator.php
src/phancap/Config.php
src/phancap/Options.php
src/phancap/Repository.php
www/get.php

index f699784..d2f14ac 100644 (file)
@@ -1,2 +1,3 @@
 /www/imgcache
 /data/phancap.config.php
+/README.html
index 3b71795..78a67ec 100644 (file)
@@ -31,6 +31,17 @@ Screenshot parameters
   Screenshot format (``png``, ``jpg``, ``pdf``, default: ``png``)
 ``smode``
   Screenshot mode (``screen`` (4:3) or ``page`` (full website height))
+``smaxage``
+  Maximum age of screenshot in seconds.
+  ISO 8601 duration specifications accepted:
+
+  - ``P1Y`` - 1 year
+  - ``P2W`` - 2 weeks
+  - ``P1D`` - 1 day
+  - ``PT4H`` - 4 hours
+
+  The configuration file defines a minimum age that the user cannot undercut
+  (``$screenshotMinAge``), as well as a default value (``$screenshotMaxAge``).
 
 Authentication parameters
 =========================
index e918e99..73d4ae6 100644 (file)
@@ -29,7 +29,7 @@ class Authenticator
         }
 
         $timestamp = (int) $_GET['atimestamp'];
-        if ($timestamp + $config->timestampLifetime < time()) {
+        if ($timestamp + $config->timestampMaxAge < time()) {
             throw new \Exception('atimestamp too old');
         }
 
index 10aae91..4d7c7a0 100644 (file)
@@ -17,10 +17,17 @@ class Config
 
     /**
      * Credentials for access
-     * username => secret key (used for signature)
-     * @var array
+     *
+     * Array of
+     *     username => secret key
+     * entries (used for signature).
+     *
+     * Boolean true to allow access in every case,
+     * false to completely disable it.
+     *
+     * @var array|boolean
      */
-    public $access = false;
+    public $access = true;
 
     /**
      * How long requests with an old timestamp may be used.
@@ -28,13 +35,38 @@ class Config
      *
      * @var integer
      */
-    public $timestampLifetime = 172800;
+    public $timestampMaxAge = 'P2D';
+
+    /**
+     * Cache time of downloaded screenshots.
+     * When the file is as older than this, it gets re-created.
+     * The user can override that using the "smaxage" parameter.
+     *
+     * Defaults to 1 week.
+     *
+     * @var integer Lifetime in seconds
+     */
+    public $screenshotMaxAge = 'P1W';
+
+    /**
+     * Minimum age of a screeshot.
+     * A user cannot set the max age parameter below it.
+     *
+     * Defaults to 1 hour.
+     *
+     * @var integer Minimum lifetime in seconds
+     */
+    public $screenshotMinAge = 'PT1H';
 
 
     public function __construct()
     {
         $this->cacheDir    = getcwd() . '/imgcache/';
         $this->cacheDirUrl = $this->getCurrentUrlDir() . '/imgcache/';
+
+        $this->timestampMaxAge  = Options::validateAge($this->timestampMaxAge);
+        $this->screenshotMaxAge = Options::validateAge($this->screenshotMaxAge);
+        $this->screenshotMinAge = Options::validateAge($this->screenshotMinAge);
     }
 
     public function load()
index ddc9bdd..b51f2a7 100644 (file)
@@ -54,6 +54,12 @@ class Options
             'default' => 'screen',
             'type'    => array('screen', 'page'),
         ),
+        'smaxage' => array(
+            'title'   => 'Maximum age for a screenshot',
+            'default' => null,
+            'type'    => 'age',
+            'min'     => null,
+        ),
         /**
          * Authentication
          */
@@ -76,6 +82,11 @@ class Options
 
     public $values = array();
 
+    /**
+     * @var Config
+     */
+    protected $config;
+
 
     /**
      * Parses an array of options, validates them and writes them into
@@ -106,6 +117,12 @@ class Options
                 $this->values[$name] = $this->validateArray(
                     $arValues[$name], $arOption['type']
                 );
+            } else if ($arOption['type'] == 'age') {
+                $this->values[$name] = $this->clamp(
+                    static::validateAge($arValues[$name]),
+                    $arOption['min'], null,
+                    true
+                );
             } else if ($arOption['type'] != 'skip') {
                 throw new \InvalidArgumentException(
                     'Unsupported option type: ' . $arOption['type']
@@ -149,6 +166,63 @@ class Options
         }
     }
 
+    protected function clamp($value, $min, $max, $silent = false)
+    {
+        if ($min !== null && $value < $min) {
+            if ($silent) {
+                $value = $min;
+            } else {
+                throw new \InvalidArgumentException(
+                    'Value must be at least ' . $min
+                );
+            }
+        }
+        if ($max !== null && $value > $max) {
+            if ($silent) {
+                $value = $max;
+            } else {
+                throw new \InvalidArgumentException(
+                    'Value may be up to ' . $min
+                );
+            }
+        }
+        return $value;
+    }
+
+    /**
+     * Validates an age is numeric. If it is not numeric, it's interpreted as
+     * a ISO 8601 duration specification.
+     *
+     * @param string $value Age in seconds
+     *
+     * @return integer Age in seconds
+     *
+     * @throws \InvalidArgumentException
+     * @link   http://en.wikipedia.org/wiki/Iso8601#Durations
+     */
+    public static function validateAge($value)
+    {
+        if (!is_numeric($value)) {
+            //convert short notation to seconds
+            $value = 'P' . ltrim(strtoupper($value), 'P');
+            try {
+                $interval = new \DateInterval($value);
+            } catch (\Exception $e) {
+                throw new \InvalidArgumentException(
+                    'Invalid age: ' . $value
+                );
+            }
+            $value = 86400 * (
+                $interval->y * 365
+                + $interval->m * 30
+                + $interval->d
+            ) + $interval->h * 3600
+                + $interval->m * 60
+                + $interval->s;
+        }
+        return $value;
+    }
+
     protected function validateArray($value, $options)
     {
         if (array_search($value, $options) === false) {
@@ -168,17 +242,7 @@ class Options
             );
         }
         $value = (int) $value;
-        if ($value < $min) {
-            throw new \InvalidArgumentException(
-                'Value must be at least ' . $min
-            );
-        }
-        if ($value > $max) {
-            throw new \InvalidArgumentException(
-                'Value may be up to ' . $min
-            );
-        }
-        return $value;
+        return $this->clamp($value, $min, $max);
     }
 
     protected function validateUrl($url)
@@ -188,12 +252,19 @@ class Options
             throw new \InvalidArgumentException('Invalid URL');
         }
         if (!isset($parts['scheme'])) {
-            throw new \InvalidArgumentException('URL scheme missing');            
+            throw new \InvalidArgumentException('URL scheme missing');
         }
         if (!isset($parts['host'])) {
-            throw new \InvalidArgumentException('URL host missing');            
+            throw new \InvalidArgumentException('URL host missing');
         }
         return $url;
     }
+
+    public function setConfig(Config $config)
+    {
+        $this->config = $config;
+        static::$options['smaxage']['default'] = $this->config->screenshotMaxAge;
+        static::$options['smaxage']['min']     = $this->config->screenshotMinAge;
+    }
 }
 ?>
index 2058bb6..157a062 100644 (file)
@@ -13,7 +13,7 @@ class Repository
         $name = $this->getFilename($options);
         $img = new Image($name);
         $img->setConfig($this->config);
-        if (!$this->isAvailable($img)) {
+        if (!$this->isAvailable($img, $options)) {
             $this->render($img, $options);
         }
         return $img;
@@ -21,18 +21,34 @@ class Repository
 
     public function getFilename(Options $options)
     {
-        return parse_url($options->values['url'], PHP_URL_HOST)
-            . '-' . md5(\serialize($options->values))
-            . '.' . $options->values['sformat'];
+        $optValues = $options->values;
+        unset($optValues['smaxage']);
+        unset($optValues['atimestamp']);
+        unset($optValues['asignature']);
+        unset($optValues['atoken']);
+
+        return parse_url($optValues['url'], PHP_URL_HOST)
+            . '-' . md5(\serialize($optValues))
+            . '.' . $optValues['sformat'];
     }
 
-    public function isAvailable(Image $img)
+    /**
+     * Check if the image is available locally.
+     *
+     * @return boolean True if we have it and it's within the cache lifetime,
+     *                 false if the cache expired or the screenshot does not
+     *                 exist.
+     */
+    public function isAvailable(Image $img, Options $options)
     {
         $path = $img->getPath();
         if (!file_exists($path)) {
             return false;
         }
-        //FIXME: add cache lifetime check
+
+        if (filemtime($path) < time() - $options->values['smaxage']) {
+            return false;
+        }
 
         return true;
     }
index 3d7395f..1da3e8a 100644 (file)
@@ -17,6 +17,7 @@ $config->load();
 
 $options = new Options();
 try {
+    $options->setConfig($config);
     $options->parse($_GET);
 } catch (\InvalidArgumentException $e) {
     header('HTTP/1.0 400 Bad Request');