cache.lua

-- Coypright 2015-2017 Stefan Göbel.
--
-- This file is part of Corky.
--
-- Corky is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
-- License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
-- later version.
--
-- Corky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
-- warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
-- details.
--
-- You should have received a copy of the GNU General Public License along with Corky. If not, see
-- <http://www.gnu.org/licenses/>.

-------------------------------------------------------------------------------------------------------------------

--- A `conky_parse()` cache for *Corky*.
--
--  **Loading the module:**
--
--    #: include, corky.cache
--
--  **General options:**
--
--    #: option, corky.cache.default_update_interval, <number>
--
--  The `corky.cache.default_update_interval` option sets the update value for cache entries that are not
--  explicitely added in the configuration file or via set(). It must be a numerical (integer) value
--  greater than or equal to zero. The default is `1`.
--
--  **Adding cache entries:**
--
--    #: cache, <text>, <update>[, <min>[, <max>]]
--
--  The `<text>` parameter specifies the text to be parsed by `conky_parse()` (e.g. `"${cpu cpu1}"`). Every valid
--  string for `conky_parse()` is allowed, as long as it is not empty.
--
--  The `<update>` parameter specifies the update interval of the value. If this is set to `1`, it will be updated
--  on every *Conky* update cycle, if it is set to `2` it will be updated every two cycles etc. If `<update>` is
--  set to `0`, it will be updated only once when the value is first requested. `<update>` must be an integer
--  greater than or equal to zero.
--
--  The first two parameters are mandatory.
--
--  The two parameters `<min>` and `<max>` are optional. They may be used to set the minimum and maximum values for
--  the specified (parsed) `<text>`, which can be used to convert the raw value into a percentage value. This
--  obviously only works if the parsed `<text>` is a pure numerical value. To specify only the `max` value, leave
--  the `min` field empty. If both are specified, the `max` value must be greater than the `min` value. Both values
--  must be valid numbers. Note that if the actual value for the parsed `<text>` is outside of the spcified
--  [`<min>`, `<max>`] range, or if no `min` and/or no `max` values have been set, the minimum and/or the maximum
--  value will be adjusted when percent() is called as required.
--
--  If a value is requested that has not been explicitely added to the cache via the configuration file (or in a
--  Lua script by using the set() function), it will be added automatically. Its update interval will
--  be set to the value of the `corky.cache.default_update_interval` option, see above. The minimum and maximum
--  values will be adjusted at runtime as required.
--
--  **Develper information:**
--
--  The cache must know the current number of *Conky* updates and maintains an internal update counter. This
--  counter is not updated automatically! To increase the counter, the main module must call the
--  update() function, preferably in the `lua_draw_hook_pre` function, before any values are
--  retrieved during the current update cycle. The cached values however will only be updated (if required
--  according to their update interval) when they are actually requested from the cache. For a value that is never
--  requested, `conky_parse()` will never be called!
--
--  @MO corky.cache
--  @CO © 2015-2017 Stefan Göbel
--  @RE 2017033001
--  @LI [GPLv3](http://www.gnu.org/copyleft/gpl.html)
--  @AU Stefan Göbel [[⌂]](http://subtype.de/) [[✉]](mailto:corky@subtype.de)
--  @AL this

-------------------------------------------------------------------------------------------------------------------

--- Stores the cache entries.
--
--  The keys in the table are the texts that should be parsed by `conky_parse()`. The values are tables with the
--  following keys:
--
--  * `last`   - The value of the update counter at the last update of the value, or `nil` before the first update.
--  * `min`    - The minimum value, may be `nil` if not set and percent() has not been called yet.
--  * `max`    - The maximum value, may be `nil` if not set and percent() has not been called yet.
--  * `update` - The update interval.
--  * `value`  - The cached value, as returned by `conky_parse()`, or `nil` before the first update.
--
--  @l cache

-------------------------------------------------------------------------------------------------------------------

local updates = 0
local this    = {}
local cache   = {}

-------------------------------------------------------------------------------------------------------------------

--- Increase (or set) the internal update counter.
--
--  This function should be called by the main module **once** per *Conky* update cycle, preferably before any
--  values are retrieved from the cache. If the parameter is specified, the update counter will be set to its
--  value. If it is not specified, the update counter will be increased by one. The initial value of the counter is
--  `0`.
--
--  @i[opt] count The new value of the internal update counter. Must be an integer greater than or equal to zero.

this.update = function (count)
   if not count then
      updates = updates + 1
   else
      updates = count
   end
end

-------------------------------------------------------------------------------------------------------------------

--- Get the value of the internal update counter.
--
--  Depending on the main module's usage of the update() function the value should roughly equal
--  *Conky*'s `${updates}`.
--
--  @I The value of the cache's internal update counter. Will be `0` if `update()` has never been called.

this.updates = function ()
   return updates
end

-------------------------------------------------------------------------------------------------------------------

do

   local conky   = require "corky.conky"
   local options = require "corky.options"

   local function update_cache (text)
      cache [text].last  = updates
      cache [text].value = conky.parse (text)
   end

   --- Retrieve a value from the cache.
   --
   --  The parameter specifies the value to be parsed by `conky_parse()`. The value will be updated if required,
   --  else the cached value will be returned. If there is no cache entry for the specified text, it will be added,
   --  with the update interval set to the default value (`1` by default, may be changed using the configuration
   --  option `corky.cache.default_update_interval`, see above).
   --
   --  @s text The text to be parsed by `conky_parse()` to get the desired value.
   --  @r The return value of `conky_parse` (either the cached value from a previous call to get() or
   --  the current value, depending on the entry's update interval and the time of the last update).

   this.get = function (text)

      if not cache [text] then
         this.set (text, options.get ("corky.cache.default_update_interval"))
      end

      if not cache [text].last then
         update_cache (text)
      elseif cache [text].update > 0 and updates - cache [text].last >= cache [text].update then
         update_cache (text)
      end

      return cache [text].value

   end

end

-------------------------------------------------------------------------------------------------------------------

--- Return a cached value in percent.
--
--  Like get(), but instead of returning the raw value of the parsed text this function returns the
--  value in percent (based on the range of the value set by the `min` and `max` options in the configuration file
--  or supplied to set(), or automatically determined minimum and maximum values).
--
--  If the current value exceeds the currently set range, or no range has been set yet, the minimum and/or maximum
--  value will be adjusted automatically. If, as a result of this, the minimum and maximum values are the same (and
--  thus the same as the current raw value), this function will return `100`.
--
--  This function internally uses get() to retrieve the raw value, see there for some more details. If
--  the raw value can not be converted to a number, this function returns `nil`.
--
--  @s text The text to be parsed by `conky_parse()` to get the desired value.
--  @N The return value of `conky_parse` in percent of its range, or `nil` if there was an error.

this.percent = function (text)

   local value = tonumber (this.get (text))

   if not value then
      return nil
   end

   local min = cache [text].min
   local max = cache [text].max

   if not min or min > value then                                             -- Adjust the minimum value if it has
      min = value                                                             --    has not been set or is greater
      cache [text].min = min                                                  --    than the current value.
   end

   if not max or max < value then                                             -- As above, for the maximum value.
      max = value
      cache [text].max = max
   end

   if min == max then                                                         -- Only happens if no range was set
      return 100                                                              --    min = max = value => 100%.
   end

   if min == 0 and max == 100 then                                            -- No need to calculate anything in
      return value                                                            --    that case.
   end

   return (value - min) * 100 / (max - min)

end

-------------------------------------------------------------------------------------------------------------------

--- Add an entry to the cache, or change an existing entry.
--
--  The parameters are the same as for the `cache` configuration directive. Please see the configuration section
--  above for a detailed description. The same restrictions mentioned there apply to this function. Note that, like
--  an entry in the configuration file, this function merely adds an entry to the cache (or changes an existing
--  entry), it will be parsed the first time by `conky_parse()` when it is first requested by calling either the
--  get() or percent() function.
--
--  When the entry already exists in the cache, the values for the update interval, minimum and maximum will be set
--  to the new values (if no `min` or `max` parameter is specified the currently set values will be removed). The
--  currently cached `conky_parse`d value (if any) will also be removed, so the next call to get() or
--  percent() will run `conky_parse` for the `<text>` regardless of the time of any previous
--  update.
--
--  @s text The text to be parsed by `conky_parse()`.
--  @i update The update interval for this cache entry. Must be an integer greater than or equal to zero.
--  @n[opt] min The minimum value for this cache entry. Must be a valid number, or `nil`. If it is specified and
--  `max` is specified, too, then `min` must be less than `max`.
--  @n[opt] max The maximum value for this cache entry. Must be a valid number, or `nil`. If it is specified and
--  `min` is specified, too, then `max` must be greater than `min`.

this.set = function (text, update, min, max)

   cache [text] = {
      update = update,
      min    = min or false,
      max    = max or false,
      last   =        false,
      value  =        false,
   }

end

-------------------------------------------------------------------------------------------------------------------

do

   local config  = require "corky.config"
   local options = require "corky.options"
   local v       = require "corky.validator"

   --- Check the default update interval for validity.
   --
   --  This handler will be called when the `corky.cache.default_update_interval` option is set in the config file.
   --  It will return `true` (and set the option to the new value) if the value is a valid integer greater than or
   --  equal to zero, else `false`.
   --
   --  @s opt The option name, will be `"corky.cache.default_update_interval"`.
   --  @s val The new value.
   --  @B Returns `true` on success, `false` in case of an error.
   --
   --  @c corky.config
   --  @c corky.options

   local function check_default_update_interval (opt, val)
      local valid, value = v { v (val) . n . ge (0) . d (1) }
      if valid then
         options.set (opt, value)
      end
      return valid
   end

   options.set ("corky.cache.default_update_interval", 1, check_default_update_interval)

   --- Configuration handler for cache settings.
   --
   --  This handler will be called automatically for every `cache` directive in the configuration
   --  file.
   --
   --  @t setting Array containing the configuration directive split into its individual parts.
   --  @B If successful `true`, `false` in case of any error.
   --
   --  @c corky
   --  @c corky.config

   local function config_handler (setting)

      local valid, text, update, min, max, _ = true

      valid, _      = v { v (#setting,    valid) . ir (3, 5)                        }
      valid, text   = v { v (setting [2], valid) . nm                               }
      valid, update = v { v (setting [3], valid) . n  . ge (0) . d (1)              }
      valid, min    = v { v (setting [4], valid) . en . o . n                       }
      valid, max    = v { v (setting [5], valid) . en . o . n . ni (min) . gt (min) }

      if not valid then
         return false
      end

      this.set (text, update, min, max)

      return true

   end

   config.handler ("cache", config_handler)

end

-------------------------------------------------------------------------------------------------------------------

return this

--                                                     :indentSize=3:tabSize=3:noTabs=true:mode=lua:maxLineLen=115:
generated by LDoc 1.4.6 Last updated 2019-02-14 20:51:07