From ea1c43b6a142fcf744c6d3ccabc0f4a873b9f81b Mon Sep 17 00:00:00 2001 From: Patrick Gaskin Date: Tue, 23 Jun 2020 17:05:22 -0400 Subject: [PATCH] Implemented generator updates (closes #54) (#55) --- src/config.c | 37 +++++++++++++++++----------- src/config.h | 5 ++-- src/generator.c | 21 ++++++++++++---- src/generator.h | 24 +++++++++++++++---- src/generator_c.c | 61 +++++++++++++++++++++++++++++++++++++++++++++++ src/init.c | 54 ++++++++++++++++++++++++++++------------- 6 files changed, 160 insertions(+), 42 deletions(-) diff --git a/src/config.c b/src/config.c index 34cfeda..563d823 100644 --- a/src/config.c +++ b/src/config.c @@ -579,32 +579,39 @@ static nm_config_parse__append__ret_t nm_config_parse__append_generator(nm_confi return NM_CONFIG_PARSE__APPEND__RET_OK; } -void nm_config_generate(nm_config_t *cfg) { - NM_LOG("config: removing any previously generated items"); - for (nm_config_t *prev = NULL, *cur = cfg; cur; cur = cur->next) { - if (prev && cur->generated) { // we can't get rid of the first item and it won't be generated anyways (plus doing it this way simplifies the loop) - prev->next = cur->next; // skip the generated item - cur->next = NULL; // so we only free that item - nm_config_free(cur); - cur = prev; // continue with the new current item - } else { - prev = cur; // the item was kept, so it's now the previous one - } - } +bool nm_config_generate(nm_config_t *cfg, bool force_update) { + bool changed = false; NM_LOG("config: running generators"); for (nm_config_t *cur = cfg; cur; cur = cur->next) { if (cur->type == NM_CONFIG_TYPE_GENERATOR) { NM_LOG("config: running generator %s:%s", cur->value.generator->desc, cur->value.generator->arg); + if (force_update) + cur->value.generator->time = (struct timespec){0, 0}; + size_t sz; nm_menu_item_t **items = nm_generator_do(cur->value.generator, &sz); if (!items) { - NM_LOG("config: ... no items generated"); + NM_LOG("config: ... no new items generated"); + if (force_update) + NM_LOG("config: ... possible bug: no items were generated even with force_update"); continue; } - NM_LOG("config: ... %zu items generated", sz); + NM_LOG("config: ... %zu items generated, removing previously generated items and replacing with new ones", sz); + + changed = true; + + // remove all generated items immediately after the generator + for (nm_config_t *t_prev = cur, *t_cur = cur->next; t_cur && t_cur->generated; t_cur = t_cur->next) { + t_prev->next = t_cur->next; // skip the generated item + t_cur->next = NULL; // so we only free that item + nm_config_free(t_cur); + t_cur = t_prev; // continue with the new current item + } + + // add the new ones for (ssize_t i = sz-1; i >= 0; i--) { nm_config_t *tmp = calloc(1, sizeof(nm_config_t)); tmp->type = NM_CONFIG_TYPE_MENU_ITEM; @@ -632,6 +639,8 @@ void nm_config_generate(nm_config_t *cfg) { } } } + + return changed; } nm_menu_item_t **nm_config_get_menu(nm_config_t *cfg, size_t *n_out) { diff --git a/src/config.h b/src/config.h index b8f596d..f9463eb 100644 --- a/src/config.h +++ b/src/config.h @@ -39,8 +39,9 @@ void nm_config_files_free(nm_config_file_t *files); nm_config_t *nm_config_parse(nm_config_file_t *files, char **err_out); // nm_config_generate runs all generators synchronously and sequentially. Any -// previously generated items are automatically removed. -void nm_config_generate(nm_config_t *cfg); +// previously generated items are automatically removed if updates are required. +// If the config was modified, true is returned. +bool nm_config_generate(nm_config_t *cfg, bool force_update); // nm_config_get_menu gets a malloc'd array of pointers to the menu items // defined in the config. These pointers will be valid until nm_config_free is diff --git a/src/generator.c b/src/generator.c index 0655ab4..4451c98 100644 --- a/src/generator.c +++ b/src/generator.c @@ -3,6 +3,7 @@ #include #include #include +#include #include "action.h" #include "generator.h" @@ -13,12 +14,19 @@ nm_menu_item_t **nm_generator_do(nm_generator_t *gen, size_t *sz_out) { NM_LOG("generator: running generator (%s) (%s) (%d) (%p)", gen->desc, gen->arg, gen->loc, gen->generate); char *err; + struct timespec old = gen->time; size_t sz = (size_t)(-1); // this should always be set by generate upon success, but we'll initialize it just in case - nm_menu_item_t **items = gen->generate(gen->arg, &sz, &err); + nm_menu_item_t **items = gen->generate(gen->arg, &gen->time, &sz, &err); + + if (items && old.tv_sec == gen->time.tv_sec && old.tv_nsec == gen->time.tv_nsec) + NM_LOG("generator: bug: new items were returned, but time wasn't changed"); + + if (!old.tv_sec && !old.tv_nsec && !err && !items) + NM_LOG("generator: warning: no existing items (time == 0), but no new items or error were returned"); if (err) { if (items) - NM_LOG("generator: warning: items should be null on error"); + NM_LOG("generator: bug: items should be null on error"); NM_LOG("generator: generator error (%s) (%s), replacing with error item: %s", gen->desc, gen->arg, err); sz = 1; @@ -34,15 +42,18 @@ nm_menu_item_t **nm_generator_do(nm_generator_t *gen, size_t *sz_out) { free(err); } + if (!err && !items && (old.tv_sec != gen->time.tv_sec || old.tv_nsec != gen->time.tv_nsec)) + NM_LOG("generator: bug: the time should have been updated if new items were returned"); + if (items) { if (sz == (size_t)(-1)) - NM_LOG("generator: warning: size should have been set by generate, but wasn't"); + NM_LOG("generator: bug: size should have been set by generate, but wasn't"); if (!sz) - NM_LOG("generator: warning: items should be null when size is 0"); + NM_LOG("generator: bug: items should be null when size is 0"); for (size_t i = 0; i < sz; i++) { if (items[i]->loc) - NM_LOG("generator: warning: generator should not set the menu item location, as it will be overridden"); + NM_LOG("generator: bug: generator should not set the menu item location, as it will be overridden"); items[i]->loc = gen->loc; } diff --git a/src/generator.h b/src/generator.h index 266dc90..6a09697 100644 --- a/src/generator.h +++ b/src/generator.h @@ -5,6 +5,7 @@ extern "C" { #endif #include +#include #include "menu.h" // nm_generator_fn_t generates menu items. It must return a malloc'd array of @@ -13,29 +14,44 @@ extern "C" { // set if provided, NULL must be returned, and sz_out is undefined. If no // entries are generated, NULL must be returned with sz_out set to 0. All // strings should also be malloc'd. -typedef nm_menu_item_t **(*nm_generator_fn_t)(const char *arg, size_t *sz_out, char **err_out); +// +// time_in_out will not be NULL, and contains zero or the last modification time +// for the generator. If it is zero, the generator should generate the items as +// usual and update the time. If it is nonzero, the generator should return NULL +// without making changes if the time is up to date (the check should be as +// quick as possible), and if not, it should update the items and update the +// time to match. If the generator does not have a way of checking for updates +// quickly, it should only update the item and set the time to a nonzero value +// if the time is zero, and return NULL if the time is nonzero. Note that this +// time doesn't have to account for different arguments or multiple instances, +// as changes in those will always cause the time to be set to zero. +typedef nm_menu_item_t **(*nm_generator_fn_t)(const char *arg, struct timespec *time_in_out, size_t *sz_out, char **err_out); typedef struct { char *desc; // only used for making the errors more meaningful (it is the title) char *arg; nm_menu_location_t loc; nm_generator_fn_t generate; // should be as quick as possible with a short timeout, as it will block startup + struct timespec time; } nm_generator_t; // nm_generator_do runs a generator and returns the generated items, if any, or -// an item which shows the error returned by the generator. +// an item which shows the error returned by the generator. If NULL is returned, +// no items needed to be updated (set time to zero to force an update) (sz_out +// is undefined). nm_menu_item_t **nm_generator_do(nm_generator_t *gen, size_t *sz_out); #define NM_GENERATOR(name) nm_generator_##name #ifdef __cplusplus -#define NM_GENERATOR_(name) extern "C" nm_menu_item_t **NM_GENERATOR(name)(const char *arg, size_t *sz_out, char **err_out) +#define NM_GENERATOR_(name) extern "C" nm_menu_item_t **NM_GENERATOR(name)(const char *arg, struct timespec *time_in_out, size_t *sz_out, char **err_out) #else -#define NM_GENERATOR_(name) nm_menu_item_t **NM_GENERATOR(name)(const char *arg, size_t *sz_out, char **err_out) +#define NM_GENERATOR_(name) nm_menu_item_t **NM_GENERATOR(name)(const char *arg, struct timespec *time_in_out, size_t *sz_out, char **err_out) #endif #define NM_GENERATORS \ X(_test) \ + X(_test_time) \ X(kfmon) #define X(name) NM_GENERATOR_(name); diff --git a/src/generator_c.c b/src/generator_c.c index a113e29..3b1a28a 100644 --- a/src/generator_c.c +++ b/src/generator_c.c @@ -1,7 +1,13 @@ #define _GNU_SOURCE // asprintf +#include #include #include #include +#include +#include +#include +#include +#include #include "generator.h" #include "menu.h" @@ -11,6 +17,9 @@ NM_GENERATOR_(_test) { #define NM_ERR_RET NULL + if (time_in_out->tv_sec || time_in_out->tv_nsec) + NM_RETURN_OK(NULL); // updates not supported (or needed, for that matter) + char *tmp; long n = strtol(arg, &tmp, 10); NM_ASSERT(*arg && !*tmp && n >= 0 && n <= 10, "invalid count '%s': must be an integer from 1-10", arg); @@ -31,15 +40,66 @@ NM_GENERATOR_(_test) { items[i]->action->on_success = true; } + clock_gettime(CLOCK_REALTIME, time_in_out); // note: any nonzero value would work, but this generator is for testing and as an example + *sz_out = n; NM_RETURN_OK(items); #undef NM_ERR_RET } +NM_GENERATOR_(_test_time) { + #define NM_ERR_RET NULL + + if (arg && *arg) + NM_RETURN_ERR("_test_time does not accept any arguments"); + + // note: this used as an example and for testing + + NM_LOG("_test_time: checking for updates"); + + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + + struct tm lt; + localtime_r(&ts.tv_sec, <); + + if (time_in_out->tv_sec && ts.tv_sec - time_in_out->tv_sec < 10) { + NM_LOG("_test_time: last update is nonzero and last update time is < 10s, skipping"); + NM_RETURN_OK(NULL); + } + + NM_LOG("_test_time: updating"); + + // note: you'd usually do the slower logic here + + nm_menu_item_t **items = calloc(1, sizeof(nm_menu_item_t*)); + items[0] = calloc(1, sizeof(nm_menu_item_t)); + asprintf(&items[0]->lbl, "%d:%02d:%02d", lt.tm_hour, lt.tm_min, lt.tm_sec); + items[0]->action = calloc(1, sizeof(nm_menu_action_t)); + items[0]->action->act = NM_ACTION(dbg_msg); + items[0]->action->arg = strdup("It worked!"); + items[0]->action->on_failure = true; + items[0]->action->on_success = true; + + time_in_out->tv_sec = ts.tv_sec; + + *sz_out = 1; + NM_RETURN_OK(items); + + #undef NM_ERR_RET +} + NM_GENERATOR_(kfmon) { #define NM_ERR_RET NULL + struct stat sb; + if (stat(KFMON_IPC_SOCKET, &sb)) + NM_RETURN_ERR("error checking '%s': stat: %s", KFMON_IPC_SOCKET, strerror(errno)); + + if (time_in_out->tv_sec == sb.st_mtim.tv_sec && time_in_out->tv_nsec == sb.st_mtim.tv_nsec) + NM_RETURN_OK(NULL); + // Default with no arg or an empty arg is to request a gui-listing const char *kfmon_cmd = NULL; if (!arg || !*arg || !strcmp(arg, "gui")) { @@ -85,6 +145,7 @@ NM_GENERATOR_(kfmon) { // Destroy the list now that we've dumped it into an array of nm_menu_item_t kfmon_teardown_list(&list); + *time_in_out = sb.st_mtim; NM_RETURN_OK(items); #undef NM_ERR_RET diff --git a/src/init.c b/src/init.c index 60e2ff1..5c94047 100644 --- a/src/init.c +++ b/src/init.c @@ -154,12 +154,13 @@ static void nm_global_config_replace(nm_config_t *cfg, const char *err) { nm_global_menu_config = cfg; nm_global_menu_config_items = nm_config_get_menu(cfg, &nm_global_menu_config_n); - if (!nm_global_menu_config_items) + if (!nm_global_menu_config_items) NM_LOG("could not allocate memory"); } bool nm_global_config_update(char **err_out) { #define NM_ERR_RET true + char *err; NM_LOG("global: scanning for config files"); @@ -170,27 +171,46 @@ bool nm_global_config_update(char **err_out) { nm_global_config_replace(NULL, err); NM_RETURN_ERR("scan for config files: %s", err); } - NM_LOG("global:%s changes detected", updated ? "" : " no"); - if (!updated) - NM_RETURN_OK(false); - - NM_LOG("global: parsing new config"); - nm_config_t *cfg = nm_config_parse(nm_global_menu_config_files, &err); - if (err) { - NM_LOG("... error: %s", err); - NM_LOG("global: freeing old config and replacing with error item"); - nm_global_config_replace(NULL, err); - NM_RETURN_ERR("parse config files: %s", err); + + if (updated) { + NM_LOG("global: parsing new config"); + nm_config_t *cfg = nm_config_parse(nm_global_menu_config_files, &err); + if (err) { + NM_LOG("... error: %s", err); + NM_LOG("global: freeing old config and replacing with error item"); + nm_global_config_replace(NULL, err); + NM_RETURN_ERR("parse config files: %s", err); + } + + NM_LOG("global: config updated, freeing old config and replacing with new one"); + nm_global_config_replace(cfg, NULL); + NM_LOG("global: done swapping config"); } NM_LOG("global: running generators"); - nm_config_generate(cfg); + bool g_updated = nm_config_generate(nm_global_menu_config, false); + NM_LOG("global:%s generators updated", g_updated ? "" : " no"); - NM_LOG("global: freeing old config and replacing with new one"); - nm_global_config_replace(cfg, NULL); + if (g_updated) { + NM_LOG("global: generators updated, freeing old items and replacing with new ones"); + + if (nm_global_menu_config_n) + nm_global_menu_config_n = 0; + + if (nm_global_menu_config_items) { + free(nm_global_menu_config_items); + nm_global_menu_config_items = NULL; + } + + nm_global_menu_config_items = nm_config_get_menu(nm_global_menu_config, &nm_global_menu_config_n); + if (!nm_global_menu_config_items) + NM_LOG("could not allocate memory"); + + NM_LOG("done replacing items"); + } + + NM_RETURN_OK(updated || g_updated); - NM_LOG("global: done swapping config"); - NM_RETURN_OK(true); #undef NM_ERR_RET }