.. _50-conditions:
Conditions
==========
*Conditions* are at the heart of **whenever**, by triggering the execution of tasks. As
mentioned before, several types of condition are supported. Part of the configuration is
common to all conditions, that is:
.. list-table::
:header-rows: 1
* - Entry
- Default
- Description
* - ``name``
- N/A
- the unique name of the condition (mandatory)
* - ``type``
- N/A
- string describing the type of condition (mandatory, one of the possible values)
* - ``recurring``
- *false*
- if *false*, the condition is not checked anymore after first successful verification
* - ``max_tasks_retries``
- 0
- how many times the tasks will be retried, when at least one of them fails, in non recurring
conditions
* - ``execute_sequence``
- *true*
- if *true* the associated tasks are executed one after the other, in the order in which they
are listed
* - ``break_on_success``
- *false*
- if *true*, task execution stops after the first successfully executed task (when
``execute_sequence`` is *true*\ )
* - ``break_on_failure``
- *false*
- if *true*, task execution stops after the first failed task (when ``execute_sequence`` is
*true*\ )
* - ``suspended``
- *false*
- if *true*, the condition will not be checked nor the associated tasks executed
* - ``tasks``
- ``[]``
- a list of task names that will be executed upon condition verification
When ``execute_sequence`` is set to *false*, the associated tasks are started concurrently in the
same instant, and task outcomes are ignored. Otherwise a minimal control flow is implemented,
allowing the sequence to be interrupted after the first success or failure in task execution. Note
that it is possible to set both ``break_on_success`` and ``break_on_failure`` to *true*.\ [#fn-1]_
The ``type`` entry can be one of: ``"interval"``, ``"time"``, ``"idle"``, ``"command"``,
``"lua"``, ``"event"``, ``"dbus"``, and ``"wmi"``. Any other value is considered a
configuration error.
.. note::
The ``"dbus"`` and ``"wmi"`` values will be considered an error if the respective features are
not available.
For conditions that should be periodically checked and whose associated task list has to be run
*whenever* they occur (and not just after the first occurrence), the ``recurring`` entry can be set
to *true*. Conditions with no associated tasks (eg. when the user comments out all the associated
tasks in the configuration file) are not checked.
The ``max_tasks_retries`` field indicates how many times the execution of a failed task, or a set
of tasks of which *at least one* has failed, will be retried in non recurring conditions when the
condition check is successful: a value of *N* indicates that the check will be performed at most
*N* times after the first failure in tasks before giving up checking. If all the tasks succeed at
the first time or within the provided number of retries, the non recurring condition will cease
its checks anyway. A provided value of ``-1`` instructs **whenever** that the checks must continue
indefinitely until all the associated tasks succeed, a value of ``0`` indicates that there should
be no retries after the first unsuccessful run. Values below ``-1`` are not admitted. This
parameter is ignored in recurring conditions. Tasks whose outcome is not checked do **not** count
as unsuccessful. If ``break_on_success`` is *true* and the first task in a sequence has a positive
oucome, the entire sequence is considered successful: in other words, only tasks that have been
executed count for outcome verification.
The ``suspended`` entry can assume a *true* value for conditions for which the user does not want
to remove the configuration but should be (at least temporarily) prevented. However, a condition
that is suspended by configuration can be awakened using an interactive command (usually by a
wrapper): :ref:`input commands <70-intcli-input-commands>` passed via the *stdin* based interface
can be used to suspend and resume condition checks when the scheduler is running.
There is another optional entry, namely ``tags``, that is accepted in item configuration: this
entry is ignored by **whenever** itself, however it is checked for correctness at startup and the
configuration is refused if not set to an array (of strings) or a table.
Another entry is common to several condition types, that is `check_after`: it can be set to the
number of seconds that **whenever** has to wait after startup (and after the last check for
_recurring_ conditions) for a subsequent check: this is useful for conditions that can run on a
more relaxed schedule, or whose check process has a significant cost in terms of resources, or
whose associated task sequence might take a long time to finish. Simpler conditions and conditions
based on time do not accept this entry.
Some condition types can set the ``recur_after_failed_check`` flag: it allows for avoidance of
multiple subsequent task runs in case of a persistent situation that causes *recurring* condition
checks to be successful: the associated tasks are run as soon as the check is successful for the
first time, then task execution is prevented as long as this status persists. After at least one
unsuccessful condition check (in which, of course, the tasks are not executed), at the following
successful one the task run is performed again.
While a condition check or the execution of an associated task sequence is underway, the condition
is marked as *busy*, and while a condition is in this state no further checks are performed. The
condition is released from its *busy* state only after all checks and tasks have been performed.
This is important when long-running checks and tasks are requested, as this flag ensures that
checks and tasks for a single long-running and recurring activity cannot overlap.
Note that all listed tasks must be defined, otherwise an error is raised and **whenever** will not
start.
The following paragraphs describe in detail each condition type. For the sake of brevity, only
specific configuration entries will be described for each type.
All *condition* definition sections must start with the TOML ``[[condition]]`` header.
.. _50-conditions-interval:
Interval
--------
*Interval* based conditions are verified after a certain amount of time has passed, either since
startup or after the last successful check. This type of condition is useful for tasks that should
be executed periodically, thus most of the times ``recurring`` will be set to *true* for this type
of condition. The following is an example of interval based condition:
.. code-block:: toml
[[condition]]
name = "IntervalConditionName"
type = "interval"
interval_seconds = 3600
# optional parameters (if omitted, defaults are used)
recurring = false
max_tasks_retries = 0
execute_sequence = true
break_on_failure = false
break_on_success = false
suspended = true
tasks = [
"Task1",
"Task2",
]
describing a condition that is verified one hour after **whenever** has started, and not anymore
after the first occurrence -- because ``recurring`` is *false* here and no retries are allowed.
Were it *true*, the condition would be verified *every* hour.
The specific parameters for this type of condition are:
.. list-table::
:header-rows: 1
* - Entry
- Default
- Description
* - ``type``
- N/A
- has to be set to ``"interval"`` (mandatory)
* - ``interval_seconds``
- N/A
- the number of seconds to wait for the condition to be verified (mandatory)
The check for this type of condition is never randomized.
.. _50-conditions-time:
Time
----
*Time* based conditions occur just after one of the provided time specifications has been reached.
Time specifications are given as a list of tables, each of which can contain one or more of the
following entries:
* ``hour``: the hour, as an integer between 0 and 23
* ``minute``: the minute, as an integer between 0 and 59
* ``second``: the second, as an integer between 0 and 59
* ``year``: an integer expressing the (full) year
* ``month``: an integer expressing the month, between 1 (January) and 12 (December)
* ``day``: an integer expressing the day of the month, between 1 and 31
* ``weekday``: the name of the weekday in English, either whole or abbreviated to three letters.
Not all the entries must be specified: for instance, specifying the day of week and a full date (as
year, month, date) may cause the event to never occur if that particular date does not occur on
that specific week day. Normally a day of the month will be specified, and then a time of the day,
or a weekday and a time of the day. However full freedom is given in specifying or omitting part of
the date:
* missing parts in the date will be considered verified at every change of each of them (years,
months, days, and weekdays)
* a missing hour specification will be considered verified at every hour
* a missing minute or second specification will be considered verified respectively at the first
minute of the hour and first second of the minute.
Of course, all the time specifications in the provided list will be checked at each tick: this
allows complex configurations for actions that must be performed at specific times.
A sample configuration section follows:
.. code-block:: toml
[[condition]]
name = "TimeConditionName"
type = "time" # mandatory value
# optional parameters (if omitted, defaults are used)
time_specifications = [
{ hour = 17, minute = 30 },
{ hour = 12, minute = 0, weekday = "wed" },
]
recurring = true
execute_sequence = true
break_on_failure = false
break_on_success = false
suspended = true
tasks = [
"Task1",
"Task2",
]
for a condition that is verified everyday at 5:30PM and every Wednesday at noon. The specific
parameters are:
.. list-table::
:header-rows: 1
* - Entry
- Default
- Description
* - ``type``
- N/A
- has to be set to ``"time"`` (mandatory)
* - ``time_specifications``
- ``{}``
- a list of *partial* time specifications, as inline tables consisting of the above described
entries (mandatory)
The check for this type of condition is never randomized.
.. _50-conditions-idle:
Idle session
------------
Conditions of the *idle* type are verified after the session has been idle (that is, without user
interaction), for the specified number of seconds.\ [#fn-2]_ This does normally not interfere with
other idle time based actions provided by the environment such as screensavers, and automatic
session lock. The following is a sample configuration for this type of condition:
.. code-block:: toml
[[condition]]
name = "IdleConditionName"
type = "idle"
idle_seconds = 3600
# optional parameters (if omitted, defaults are used)
recurring = true
execute_sequence = true
break_on_failure = false
break_on_success = false
suspended = true
tasks = [
"Task1",
"Task2",
]
for a condition that will be verified each time that an hour has passed since the user has been
away from the mouse and the keyboard. For tasks that usually occur only once per session when the
workstation is idle (such as backups, for instance), ``recurring`` can be set to *false*. The
table below describes the specific configuration entries:
.. list-table::
:header-rows: 1
* - Entry
- Default
- Description
* - ``type``
- N/A
- has to be set to ``"idle"`` (mandatory)
* - ``idle_seconds``
- N/A
- the number of idle seconds to be waited for in order to consider the condition verified
(mandatory)
The check for this type of condition is never randomized.
.. _50-conditions-command:
Command
-------
This type of condition gives the possibility to execute an OS *command* and decide whether or not
the condition is verified testing the command exit code and/or what the command writes on its
standard output or standard error channel. The available checks are of the same type as the ones
available for command based tasks. In fact it is possible to:
* identify a provided exit code as a failure or as a success
* specify that the presence of a substring or matching a regular expression in what the command
provides as output (on both *stdout* and *stderr*) corresponds to either a failure or a success.
When a success rule is specified (that is, one of the parameters with the ``success_`` prefix),
the verification of that rule is considered a successful outcome, and outcomes that do not satisfy
any given success rule are identified as failures. On the other hand, if a failure rule is set,
then outcomes that match that rule are considered failures: any other outcome is successful.
.. tip::
Setting both success and failure rules is actually possible, however it is not really useful
because of the priority that is used when checking for success or failure in the execution
of a command. Success is checked first, and in the following order:
* exit code correspondance
* standard input search or match
* standard error search or match
and then, if none of these rules (if provided) is matched, failure is checked in the same
order. Therefore, if one or more success rules are given, not matching them is considered
a failure anyway and the failure rules are simply skipped.
Most of the times it is useful to just provide one or two criteria, either on the success
or on the failure side only.
As for command based tasks, it is not mandatory to follow the usual conventions -- this means,
for instance, that a zero exit code can be identified as a failure and a non-zero exit code as a
success.
If a command is known to have the possibility to hang, a timeout can be configured by specifying
the maximum number of seconds to wait for the process to exit: after this amount of time the
process is terminated and fails.
An example of command based condition follows:
.. code-block:: toml
[[condition]]
name = "CommandConditionName"
type = "command" # mandatory value
startup_path = "/some/startup/directory" # must exist
command = "executable_name"
command_arguments = [
"arg1",
"arg2",
]
# optional parameters (if omitted, defaults are used)
recurring = false
max_tasks_retries = 3
execute_sequence = true
break_on_failure = false
break_on_success = false
suspended = false
tasks = [
"Task1",
"Task2",
]
check_after = 10
recur_after_failed_check = true
match_exact = false
match_regular_expression = false
success_stdout = "expected"
success_stderr = "expected_error"
success_status = 0
failure_stdout = "unexpected"
failure_stderr = "unexpected_error"
failure_status = 2
timeout_seconds = 60
case_sensitive = false
include_environment = true
set_environment_variables = true
environment_variables = { VARNAME1 = "value1", VARNAME2 = "value2" }
Note that the ``recurring`` flag is ``false``, and ``max_tasks_retries`` is set to *3*: this
means that the check will be performed *three more times* after the first unsuccessful run of
the associated tasks.
The following table illustrates the parameters specific to *command* based conditions:
.. list-table::
:header-rows: 1
* - Entry
- Default
- Description
* - ``type``
- N/A
- has to be set to ``"interval"`` (mandatory)
* - ``check_after``
- (empty)
- number of seconds that have to pass before the condition is checked the first time or
further times if ``recurring`` is *true*
* - ``recur_after_failed_check``
- *false*
- if set to *true* and ``recurring`` is also *true*, persistent successful checks after
the first one do not run associated tasks
* - ``startup_path``
- N/A
- the directory in which the command is started (mandatory)
* - ``command``
- N/A
- path to the executable (mandatory; if the path is omitted, the executable should be
found in the search *PATH*\ )
* - ``command_arguments``
- N/A
- arguments to pass to the executable: can be an empty list, ``[]`` (mandatory)
* - ``match_exact``
- *false*
- if *true*, the entire output is matched instead of searching for a substring
* - ``match_regular_expression``
- *false*
- if *true*, the match strings are considered regular expressions instead of substrings
* - ``case_sensitive``
- *false*
- if *true*, substring search or match and regular expressions match is performed
case-sensitively
* - ``timeout_seconds``
- (empty)
- if set, the number of seconds to wait before the command is terminated (with
unsuccessful outcome)
* - ``success_status``
- (empty)
- if set, when the execution ends with the provided exit code the condition is
considered verified
* - ``failure_status``
- (empty)
- if set, when the execution ends with the provided exit code the condition is
considered failed
* - ``success_stdout``
- (empty)
- the substring or RE to be found or matched on *stdout* to consider the task successful
* - ``success_stderr``
- (empty)
- the substring or RE to be found or matched on *stderr* to consider the task successful
* - ``failure_stdout``
- (empty)
- the substring or RE to be found or matched on *stdout* to consider the task failed
* - ``failure_stderr``
- (empty)
- the substring or RE to be found or matched on *stderr* to consider the task failed
* - ``include_environment``
- *true*
- if *true*, the command is executed in the same environment in which **whenever** was
started
* - ``set_environment_variables``
- *true*
- if *true*, **whenever** sets environment variables reporting the names of the task
and the condition
* - ``environment_variables``
- ``{}``
- extra variables that might have to be set in the environment in which the provided
command runs
If ``set_environment_variables`` is *true*, **whenever** sets the following environment variable:
* ``WHENEVER_CONDITION`` to the unique name of the condition that is currently being tested
for scripts or other executables used in checks that might be aware of **whenever**.
For this type of condition the actual test can be performed at a random time within the tick
interval.
.. _50-conditions-lua:
Lua script
----------
A `Lua `__ script can be used to determine the verification of a condition:
after the execution of the script, one or more variables can be checked against expected values
and thus decide whether or not the associated tasks have to be run. Given the power of *Lua* and
its standard library, this type of condition can constitute a lightweight alternative to complex
scripts to call to implement a *command* based condition. The definition of a *Lua* condition is
actually much simpler:
.. code-block:: toml
[[condition]]
name = "LuaConditionName"
type = "lua" # mandatory value
script = '''
log.info("hello from Lua");
result = 10;
'''
# optional parameters (if omitted, defaults are used)
recurring = false
max_tasks_retries = -1
execute_sequence = true
break_on_failure = false
break_on_success = false
suspended = false
tasks = [
"Task1",
"Task2",
]
check_after = 10
recur_after_failed_check = false
expect_all = false
expected_results = { result = 10 }
Note that the ``recurring`` flag is ``false``, and ``max_tasks_retries`` is set to *-1*: this
means that the check will be performed until **all** the associated tasks are executed
successfully.
The specific parameters are described in the following table:
.. list-table::
:header-rows: 1
* - Entry
- Default
- Description
* - ``type``
- N/A
- has to be set to ``"lua"`` (mandatory)
* - ``check_after``
- (empty)
- number of seconds that have to pass before the condition is checked the first time
or further times if ``recurring`` is *true*
* - ``recur_after_failed_check``
- *false*
- if set to *true* and ``recurring`` is also *true*, persistent successful checks after
the first one do not run associated tasks
* - ``script``
- N/A
- the *Lua* code that has to be executed by the internal interpreter (mandatory)
* - ``expect_all``
- *false*
- if *true*, all the expected results have to be matched to consider the task successful,
otherwise at least one
* - ``expected_results``
- ``{}``
- a dictionary of variable names and their expected values to be checked after execution
The same rules and possibilities seen for *Lua* based tasks also apply to conditions: the embedded
*Lua* interpreter is enriched with library functions that allow to write to the **whenever** log,
at all logging levels (\ *error*, *warn*, *info*, *debug*, *trace*\ ). The library
functions are the following:
* ``log.error``
* ``log.warn``
* ``log.info``
* ``log.debug``
* ``log.trace``
and take a single string as their argument. Also, from the embedded *Lua* interpreter there is a
value that can be accessed:
* ``whenever_condition`` is the name of the condition being checked.
External scripts can be executed via ``dofile("/path/to/script.lua")`` or by using the ``require``
function. While a successful execution is always determined by matching the provided criteria, an
error in the script is always considered a failure.
The ``recur_after_failed_check`` flag allows for avoidance of multiple subsequent task runs in case
of a persistent situation that cause the condition checks to be successful if the condition is
marked as *recurring*: the associated tasks are run as soon as the check is successful for the
first time, then the tasks are not executed anymore as long as this status persists. After at least
one unsuccessful condition check (in which, of course, the tasks are not executed), at the
following successful one the task run is performed again.
For this type of condition the actual test can be performed at a random time within the tick
interval.
.. _50-conditions-dbus:
DBus method (optional)
----------------------
The return message of a *DBus method invocation* is used to determine the execution of the tasks
associated to this type of condition. Due to the nature of DBus, the configuration of a *DBus*
based condition is quite complex, both in terms of definition of the method to be invoked,
especially for what concerns the parameters to be passed to the method, and in terms of specifying
how to test the result.\ [#fn-3]_
.. note::
This type of item is only available when the ``dbus`` feature is enabled.
So, as a rule of thumb:\ [#fn-4]_
* arguments to be passed to the DBus method are specified in a list
* criteria to determine expected return values (aka *messages*, which can be complex structures)
are expressed a list of tables of three elements each (inline tables can be used for readability),
that is:
* ``"index"``: a list of elements, which can be either integers or strings representing each a
positional 0-based index or a string (or *object path*\ ) key in a dictionary; this allows to
index deeply nested structures in which part of the nested elements are dictionaries
* ``"operator"``: one of the following strings
* ``"eq"`` for *equality*
* ``"neq"`` for *inequality*
* ``"gt"`` meaning *greater than*
* ``"ge"`` meaning *greater or equal to*
* ``"lt"`` meaning *less than*
* ``"le"`` meaning *less or equal to*
* ``"match"`` to indicate that the second operand has to be intended as a *regular expression*
to be matched
* ``"contains"`` to indicate that the second operand *is contained* in the first operand
(see below)
* ``"ncontains"`` to indicate that the second operand *is not contained* in the first operand
* ``"value"``: the second operand for the specified operator.
.. note::
The first element of the ``"index"`` field is always a *zero-based integer*:
this ia because a *message* is supposed to consist of an array of numbered *fields*:
if a single value is returned, which can also be as complex as a nested dictionary,
it is considered to be the first field (having 0 as index) of the return message,
thus *0* must be specified as the first value in the index.
Please notice that not all types of operand are supported for all operators: comparisons
(\ *greater* and *greater or equal*, *less* and *less or equal*\ ) are only supported
for numbers, and matching is only supported for strings. The ``"contains"``\ /\ ``"ncontains"``
operators support non-structured types for the second operand (booleans, numbers, and strings)
and either strings (and object paths) or arrays and dictionaries for the first one: if the first
operand is an array the second operand is searched in the list and the check is true when it is
found, in case it is a dictionary then the second operand (which should be a string) is searched
among the *keys* of the dictionary, and if the first operand is either a string or an object path,
the check is true when the second one is a substring. Also, *comparisons always fail for
incompatible operands*: integers can only be compared with integers, floating point numbers with
floating point numbers and strings with strings -- no automatic type conversion is performed. This
also yields for attempts to find a value in an array: an integer will never be found in an array of
floating point numbers, and so on. To be consistent with the rule of unsuccessfulness on incompatible
operands, the ``"ncontains"`` operator too *is unsuccessful when the operands cannot be compared*,
even though, from another point of view, the opposite could have been seen as appropriate.
A further difficulty is due to the fact that, while DBus is strictly typed and supports all the
basic types supported by *C* and *C++*, TOML does not. TOML has instead more generic types, which
are listed below along with the DBus type to which **whenever** converts them in method invocation:
* *Boolean*: ``BOOLEAN``
* *Integer*: ``I64``
* *Float*: ``F64``
* *String*: ``STRING``
* *List*: ``ARRAY``
* *Map*: ``DICTIONARY``
This means that there are a lot of value types that are not directly derived from the native TOML
types. **whenever** comes to help by allowing to express strictly typed values by using specially
crafted strings. These string must begin with a backslash, ``\`` (in the TOML representation it
has to be doubled in order to escape it, or a literal string must be used), followed by the
*signature* character (\ *ASCII Type Code* in the basic type table\ [#fn-5]_\ ) identifying the
type. For example, the string ``'\y42'`` indicates a ``BYTE`` parameter holding *42* as the value,
while ``'\o/com/example/MusicPlayer1'`` indicates an ``OBJECT_PATH``\ [#fn-6]_ containing the value
*/com/example/MusicPlayer1*. A specially crafted string will be translated into a specific value of
a specific type *only* when a supported *ASCII Type Code* is used, in all other cases the string is
interpreted literally: for instance, ``'\w100'`` is translated into a ``STRING`` holding the value
*\w100*.
For return values, while the structure of complex entities received from DBus is kept, all values
are automatically converted to more generic types: a returned ``BYTE`` is converted to a TOML
*Integer*, and a returned ``OBJECT_PATH`` is consdered a TOML *String* which, as a side effect,
supports the ``"match"`` operator.
An example of *DBus* method based condition follows:
.. code-block:: toml
[[condition]]
name = "DbusMethodConditionName"
type = "dbus" # mandatory value
bus = ":session" # either ":session" or ":system"
service = "org.freedesktop.DBus"
object_path = "/org/freedesktop/DBus"
interface = "org.freedesktop.DBus"
method = "NameHasOwner"
# optional parameters (if omitted, defaults are used)
recurring = true
execute_sequence = true
break_on_failure = false
break_on_success = false
suspended = true
tasks = [ "Task1", "Task2" ]
check_after = 60
recur_after_failed_check = false
parameter_call = [
"SomeObject",
[42, "a structured parameter"],
["the following is an u64", "\\t42"],
]
parameter_check_all = false
parameter_check = [
{ index = 0, operator = "eq", value = false },
{ index = [1, 5], operator = "neq", operator = "forbidden" },
{ index = [2, "mapidx", 5], operator = "match", value = "^[A-Z][a-zA-Z0-9_]*$" },
]
As shown below, ``parameter_check`` is the list of criteria against which the *return message parameters*
(the invocation results are often referred to with this terminology in DBus jargon): for what has
been explained above, the checks are performed like this:
#. the first element (thus with 0 as index) of the returned array is expected to be a boolean and
to be *false*
#. the second element is considered to be an array, whose sixth element (with index 5) must not be
the string *"forbidden"*
#. the third element is highly nested, containing a map whose element with key *"mapidx"* is an
array, containing a string at its sixth position, which should be alphanumeric and begin with a
capital letter, and may contain underscores (that is, matches the *regular expression*
``^[A-Z][a-zA-Z0-9_]*$``\ ).
Note that the first check shows a ``0`` index not embedded in a list: if a returned parameter is
not an array or a dictionary and its value is required directly, the square brackets around this
single index can be omitted and **whenever** does not complain. Since this is probably the most
frequent use case, this is a way to make configuration more readable and concise in such cases.
Since ``parameter_check_all`` is *false*, satisfaction of one of the provided criteria is
sufficient to determine the success of the condition.
The specific parameters are described in the following table:
.. list-table::
:header-rows: 1
* - Entry
- Default
- Description
* - ``type``
- N/A
- has to be set to ``"dbus"`` (mandatory)
* - ``check_after``
- (empty)
- number of seconds that have to pass before the condition is checked the first time or further
times if ``recurring`` is *true*
* - ``recur_after_failed_check``
- *false*
- if set to *true* and ``recurring`` is also *true*, persistent successful checks after the
first one do not run associated tasks
* - ``bus``
- N/A
- the bus on which the method is invoked: must be either ``":system"`` or ``":session"``,
including the starting colon (mandatory)
* - ``service``
- N/A
- the name of the *service* that exposes the required *object* and the *interface* to invoke or
query (mandatory)
* - ``object_path``
- N/A
- the *object* exposing the *interface* to invoke or query (mandatory)
* - ``interface``
- N/A
- the *interface* to invoke or query (mandatory)
* - ``method``
- N/A
- the name of the *method* to be invoked (mandatory)
* - ``parameter_call``
- (empty)
- a structure, expressed as a list, containing exactly the parameters that shall be passed to
the method
* - ``parameter_check_all``
- *false*
- if *true*, all the provided criteria will have to be satisfied for the condition to be
successful, otherwise one is enough
* - ``parameter_check``
- (empty)
- a list of maps having the structure explained above
The value corresponding to the ``service`` entry is often referred to as *bus name* in various
documents: here *service* is preferred to avoid confusing it with the actual bus, which is
either the *session bus* or the *system bus*.
Methods resulting in an error will *always* be considered as failed: therefore it is possible
to avoid to provide return value criteria, and just consider a successful invocation as a success
and an error as a failure. Also, any errors that may arise during checks will cause the check
itself to yield *false*.
Note that DBus based conditions are supported on Windows, however DBus should be running for such
conditions to be useful -- which is very unlikely to say the least.
The ``recur_after_failed_check`` flag allows for avoidance of multiple subsequent task runs in case
of a persistent situation that cause the condition checks to be successful if the condition is
marked as *recurring*: the associated tasks are run as soon as the check is successful for the
first time, then the tasks are not executed anymore as long as this status persists. After at least
one unsuccessful condition check (in which, of course, the tasks are not executed), at the following
successful one the task run is performed again.
For this type of conditions the actual test can be performed at a random time within the tick interval.
.. _50-conditions-wmi:
WMI Query (optional, Windows only)
----------------------------------
On Windows, **whenever** allows to directly query the
`WMI `__ subsystem, which is a
powerful way to retrieve information. *WMI* is accessed via a query language called
`WQL `__, which is
syntactically and semantically close to *SQL*. Queries normally return lists of compound values
where every component has a name. For analogy with database operations and queries, this document
will refer the returned compound values as *rows* or *records*, and their components as *fields*.
.. note::
This type of item is only available when the ``wmi`` feature is enabled.
*WMI* inquiries are somewhat simpler than their *DBus* counterparts, mostly because the query
language is more structured and allows for more complex filtering at the query level. Because of
that, and since a query can be crafted in order to directly return the values of interest without
having to deeply inspect returned *objects*, **whenever** will only inspect fields containing
simple values (numbers, booleans, and strings) in a query result.
The main part of a *WMI Query* based condition is obviously the *query* itself, which is provided
as a freeform string using the mandatory ``query`` configuration entry. **whenever** does not do
any parsing or check on the provided query, thus an incorrect query will only cause the condition
test to always fail and log an error message, at least in the *debug* log level.
Since a *WMI* query returns a set of records, it is possible to filter the returned rows by
providing criteria: this can be done using the _result\ *check* entry, provided as a list of
dictionaries, each representing a check, having the following entries:
* ``index`` is the index of the row in the result set that will be checked (this sub-entry is
optional, leaving it out means *any* row)
* ``field`` indicates which record *field* is checked, and must be a string
* ``operator`` is the check operator, a string of the ones shown below
* ``value`` is the value that the field is checked against, and its type must be compatible with
the field value.
Operators resemble the ones used for *DBus* return message value comparisons, with the exception
that, since no complex values such as arrays or dictionaries are expected to be tested, operators
that check for inclusion are not available. The supported operators are, therefore:
* ``"eq"`` for *equality*
* ``"neq"`` for *inequality*
* ``"gt"`` meaning *greater than*
* ``"ge"`` meaning *greater or equal to*
* ``"lt"`` meaning *less than*
* ``"le"`` meaning *less or equal to*
* ``"match"`` to indicate that the second operand has to be intended as a *regular expression* to
be matched.
and work for all supported values in the usual way, taking out ``"match"`` that can obviously only
be applied to strings. Note that integers and floating point numbers can be compared to each other,
with all the known issues related to this kind of comparison.
An example of *WMI Query* based condition configuration is the following:
.. code-block:: toml
[[condition]]
name = "WMIQueryConditionName"
type = "wmi" # mandatory value
query = '''
SELECT * FROM Win32_LogicalDisk WHERE FileSystem = 'NTFS'
'''
# optional parameters (if omitted, defaults are used)
recurring = true
execute_sequence = true
break_on_failure = false
break_on_success = false
suspended = true
tasks = [ "Task1", "Task2" ]
check_after = 60
recur_after_failed_check = false
result_check = [
{ field = "FreeSpace", operator = "lt", value = 5_000_000_000 },
]
result_check_all = false
The above condition definition queries the *WMI* subsystem to report basic information about the
logical drives handled by the system, and is successful if any of them has roughly less than 5GB
left. The test is performed every minute. Of course, the ``result_check`` part coul also be
incorporated in the query itself, using an *AND* clause.
The specific parameters are described in the following table:
.. list-table::
:header-rows: 1
* - Entry
- Default
- Description
* - ``type``
- N/A
- has to be set to ``"wmi"`` (mandatory)
* - ``check_after``
- (empty)
- number of seconds that have to pass before the condition is checked the first time or
further times if ``recurring`` is *true*
* - ``recur_after_failed_check``
- *false*
- if set to *true* and ``recurring`` is also *true*, persistent successful checks after
the first one do not run associated tasks
* - ``query``
- N/A
- the *WQL* query used to inquire the system
* - ``result_check_all``
- *false*
- if *true*, all the provided criteria will have to be satisfied for the condition to
be successful, otherwise one is enough
* - ``result_check``
- (empty)
- a list of maps having the structure explained above
Note that it is not mandatory to provide criteria to filter the query result: their omission causes
the condition to be successful if the query *returns at least one row*. Also, omitting the index on
a check causes *that single check* to be performed on every returned row: this means that, for
instance, if all the provided checks omit the row index, even though **whenever** is instructed to
consider the test successful when *all* the provided criteria are satisfied (setting the
``result_check_all`` entry to *true*\ ), the test will be successful if there is at least one row
satisfying each check -- and not just one row satisfying *all* the checks. This is because the tests
that **whenever** applies to the result sets are intended to be simple in order to keep the
configuration file as readable as possible (and the configuration of *DBus* inquiries is a failure
in this sense). However, more complex and fine-grained criteria can be kept at the query level.
As said above, any error will cause the condition to be evaluated as unsuccessful.
.. _50-conditions-event:
Event
-----
Conditions that are fired by *events* are referred to here both as *event* conditions and as
*bucket* conditions. The reason for the second name is that every time that **whenever** catches
an event that has been required to be monitored, it tosses the associated condition in a sort of
*execution bucket*, that is checked by the scheduler at every tick: the scheduler withdraws
every condition found in the bucket and runs the associated tasks. In facts, these conditions
only exist as a connection between the events, that occur asynchronously, and the scheduler. Their
configuration is therefore very simple, as seen in this example:
.. code-block:: toml
[[condition]]
name = "BucketConditionName"
type = "bucket" # "bucket" or "event" are the allowed values
# optional parameters (if omitted, defaults are used)
recurring = false
max_tasks_retries = 0
execute_sequence = true
break_on_failure = false
break_on_success = false
suspended = false
tasks = [
"Task1",
"Task2",
]
that is, these conditions have no specific fields, if not for the mandatory ``type`` that should
be either ``"bucket"`` or ``"event"`` (with no operational difference, at least for the moment
being):
.. list-table::
:header-rows: 1
* - Entry
- Default
- Description
* - ``type``
- N/A
- has to be set to ``"bucket"`` or ``"event"`` (mandatory)
These conditions are associated to *events* for verification, no other criteria can be specified.
For this type of conditions the actual test can be performed at a random time within the tick
interval.
.. [#fn-1] In this case the execution will continue as long as the outcome is *undefined* until
the first success or failure happens.
.. [#fn-2] Except on *Wayland* based Linux systems (e.g. Ubuntu), on which the idle time starts
*after the session has been locked*.
.. [#fn-3] In fact, in the original *When* the DBus based conditions and events were considered
an advanced feature: even the dialog box that allowed the configuration of user-defined
DBus events was only available through a specific invocation using the command line.
.. [#fn-4] DBus parameters and criteria can still be expressed in `JSON `__
format for compatibility reasons, but this support will be eventually removed.
.. [#fn-5] See the `DBus Specification
`__ for the
complete list of supported types, and the ASCII character that identifies each of them.
.. [#fn-6] in DBus, strings and object paths are considered different types.