.. _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.