diff --git a/res/doc b/res/doc index a1722cf..b2ca7cc 100644 --- a/res/doc +++ b/res/doc @@ -62,10 +62,18 @@ # cmd_output - the timeout in milliseconds (0 < t < 10000), a colon, then the command line to pass to /bin/sh -c (started in /) # # chain:: +# chain_failure:: +# chain_always:: # Adds an action to the chain that began with the preceding menu_item. # Actions are performed in the order they are written. # Each chain entry MUST follow the menu_item it is attached to. Another # menu_item marks the start of the next chain. +# By default, each action only executes if the previous one was successful. +# If chain_failure is used, the action is only executed if the last one +# failed. If chain_always is used, the action is executed no matter what. +# Any error message is only displayed if the action is the last one which +# was executed in the chain (not necessarily the last one in the config +# file). # # For example, you might have a configuration file in KOBOeReader/.adds/nm/mystuff like: # diff --git a/src/config.c b/src/config.c index 1587437..6d05a75 100644 --- a/src/config.c +++ b/src/config.c @@ -108,8 +108,8 @@ nm_config_t *nm_config_parse(char **err_out) { // field 1: type char *c_typ = strtrim(strsep(&cur, ":")); if (!strcmp(c_typ, "menu_item")) { - if (it) nm_config_push_menu_item(&cfg, it); // type: menu_item + if (it) nm_config_push_menu_item(&cfg, it); it = calloc(1, sizeof(nm_menu_item_t)); cur_act = NULL; // type: menu_item - field 2: location @@ -124,8 +124,10 @@ nm_config_t *nm_config_parse(char **err_out) { if (!c_lbl) RETERR("file %s: line %d: field 3: expected label, got end of line", fn, line_n); else it->lbl = strdup(c_lbl); - nm_menu_action_t *action = calloc(1, sizeof(nm_menu_action_t)); // type: menu_item - field 4: action + nm_menu_action_t *action = calloc(1, sizeof(nm_menu_action_t)); + action->on_failure = true; + action->on_success = true; char *c_act = strtrim(strsep(&cur, ":")); if (!c_act) RETERR("file %s: line %d: field 4: expected action, got end of line", fn, line_n); #define X(name) else if (!strcmp(c_act, #name)) action->act = NM_ACTION(name); @@ -139,10 +141,22 @@ nm_config_t *nm_config_parse(char **err_out) { else action->arg = strdup(c_arg); nm_config_push_action(&cur_act, action); it->action = cur_act; - } else if (!strcmp(c_typ, "chain")) { + } else if (!strncmp(c_typ, "chain", 5)) { + // type: chain if (!it) RETERR("file %s: line %d: unexpected chain, no menu_item to link to", fn, line_n); nm_menu_action_t *action = calloc(1, sizeof(nm_menu_action_t)); + if (!strcmp(c_typ, "chain")) { + action->on_failure = false; + action->on_success = true; + } else if (!strcmp(c_typ, "chain_always")) { + action->on_failure = true; + action->on_success = true; + } else if (!strcmp(c_typ, "chain_failure")) { + action->on_failure = true; + action->on_success = false; + } else RETERR("file %s: line %d: field 1: unknown type '%s'", fn, line_n, c_typ); + // type: chain - field 2: action char *c_act = strtrim(strsep(&cur, ":")); if (!c_act) RETERR("file %s: line %d: field 2: expected action, got end of line", fn, line_n); @@ -187,8 +201,9 @@ nm_config_t *nm_config_parse(char **err_out) { size_t mm = 0, rm = 0; for (nm_config_t *cur = cfg; cur; cur = cur->next) { if (cur->type == NM_CONFIG_TYPE_MENU_ITEM) { + NM_LOG("cfg(NM_CONFIG_TYPE_MENU_ITEM) : %d:%s", cur->value.menu_item->loc, cur->value.menu_item->lbl); for (nm_menu_action_t *cur_act = cur->value.menu_item->action; cur_act; cur_act = cur_act->next) - NM_LOG("cfg(NM_CONFIG_TYPE_MENU_ITEM) : %d:%s:%p:%s", cur->value.menu_item->loc, cur->value.menu_item->lbl, cur_act->act, cur_act->arg); + NM_LOG("...cfg(NM_CONFIG_TYPE_MENU_ITEM) (%s%s%s) : %p:%s", cur_act->on_success ? "on_success" : "", (cur_act->on_success && cur_act->on_failure) ? ", " : "", cur_act->on_failure ? "on_failure" : "", cur_act->act, cur_act->arg); switch (cur->value.menu_item->loc) { case NM_MENU_LOCATION_MAIN_MENU: mm++; break; case NM_MENU_LOCATION_READER_MENU: rm++; break; diff --git a/src/menu.cc b/src/menu.cc index 4616373..958c9a0 100644 --- a/src/menu.cc +++ b/src/menu.cc @@ -143,40 +143,50 @@ extern "C" MenuTextItem* _nm_menu_hook(void* _this, QMenu* menu, QString const& // note: we're capturing by value, i.e. the pointer to the global variable, rather then the stack variable, so this is safe QObject::connect(action, &QAction::triggered, std::function([it](bool){ - NM_LOG("Item '%s' pressed...", it->lbl); - char *err; + NM_LOG("item '%s' pressed...", it->lbl); + char *err = NULL; + bool success = true; for (nm_menu_action_t *cur = it->action; cur; cur = cur->next) { - NM_LOG("running action %p with argument %s : ", cur->act, cur->arg); + NM_LOG("action %p with argument %s : ", cur->act, cur->arg); + NM_LOG("...success=%d ; on_success=%d on_failure=%d", success, cur->on_success, cur->on_failure); + if (!((success && cur->on_success) || (!success && cur->on_failure))) { + NM_LOG("...skipping action due to condition flags"); + continue; + } + free(err); nm_action_result_t *res = cur->act(cur->arg, &err); - if (err) { - NM_LOG("Got error: '%s', displaying...", err); - ConfirmationDialogFactory_showOKDialog(QString::fromUtf8(it->lbl), QString::fromUtf8(err)); - free(err); - return; - } else if (res) { - NM_LOG("Got result: type=%d msg='%s', handling...", res->type, res->msg); - MainWindowController *mwc; - switch (res->type) { - case NM_ACTION_RESULT_TYPE_SILENT: - break; - case NM_ACTION_RESULT_TYPE_MSG: - ConfirmationDialogFactory_showOKDialog(QString::fromUtf8(it->lbl), QLatin1String(res->msg)); - break; - case NM_ACTION_RESULT_TYPE_TOAST: - mwc = MainWindowController_sharedInstance(); - if (!mwc) { - NM_LOG("toast: could not get shared main window controller pointer"); - break; - } - MainWindowController_toast(mwc, QLatin1String(res->msg), QStringLiteral(""), 1500); + if (!(success = err == NULL)) { + NM_LOG("...error: '%s'", err); + continue; + } else if (!res) { + NM_LOG("...warning: you should have returned a result with type silent, not null, upon success"); + continue; + } + NM_LOG("...result: type=%d msg='%s', handling...", res->type, res->msg); + MainWindowController *mwc; + switch (res->type) { + case NM_ACTION_RESULT_TYPE_SILENT: + break; + case NM_ACTION_RESULT_TYPE_MSG: + ConfirmationDialogFactory_showOKDialog(QString::fromUtf8(it->lbl), QLatin1String(res->msg)); + break; + case NM_ACTION_RESULT_TYPE_TOAST: + mwc = MainWindowController_sharedInstance(); + if (!mwc) { + NM_LOG("toast: could not get shared main window controller pointer"); break; } - nm_action_result_free(res); - } else { - NM_LOG("warning: you should have returned a result with type silent, not null, upon success"); + MainWindowController_toast(mwc, QLatin1String(res->msg), QStringLiteral(""), 1500); + break; } + nm_action_result_free(res); } - NM_LOG("Success!"); + if (err) { + NM_LOG("last action returned error %s", err); + ConfirmationDialogFactory_showOKDialog(QString::fromUtf8(it->lbl), QString::fromUtf8(err)); + free(err); + } + NM_LOG("done"); })); } diff --git a/src/menu.h b/src/menu.h index 399ec0a..68c30ce 100644 --- a/src/menu.h +++ b/src/menu.h @@ -4,6 +4,7 @@ extern "C" { #endif +#include #include #include "action.h" @@ -15,6 +16,8 @@ typedef enum { typedef struct nm_menu_action_t { char *arg; + bool on_success; + bool on_failure; nm_action_fn_t act; // can block, must return 0 on success, nonzero with out_err set to the malloc'd error message on error struct nm_menu_action_t *next; } nm_menu_action_t;