#include #include #include #include "gm-triggers.h" #include "gm-debug.h" #define GM_TRIGGERS_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE((object), GM_TYPE_TRIGGERS, GmTriggersPrivate)) typedef struct _trigger_trans { gint type; const gchar *name; } trigger_trans; static const trigger_trans table_conditions[] = { {TCT_CONTAINS, "contains"}, {TCT_NOT_CONTAINS, "not-contains"}, {TCT_BEGINS, "begins"}, {TCT_NOT_BEGINS, "not-begins"}, {TCT_ENDS, "ends"}, {TCT_NOT_ENDS, "not-ends"}, {TCT_MATCHES, "matches"}, {TCT_NOT_MATCHES, "not-matches"}, {TCT_USER_ONLINE, "online"}, {TCT_USER_OFFLINE, "offline"}, {TCT_USER_IDLE, "idle"}, {TCT_USER_IDLE_OFF, "idle-off"}, {TCT_USER_AWAY, "away"}, {TCT_USER_AWAY_OFF, "away-off"}, {-1, NULL} }; static const trigger_trans table_actions[] = { {TAT_HIGHLIGHT_LINE, "highlight-line"}, {TAT_HIGHLIGHT_MATCH, "highlight-match"}, {TAT_BEEP, "beep"}, {TAT_PLAY_SOUND, "play-sound"}, {TAT_NOTIFY, "notify"}, #ifdef HASRUBY {TAT_RUN_SCRIPT, "run-script"}, #endif {TAT_RUN, "run"}, {-1, NULL} }; struct _GmTriggersPrivate { GList *triggers; gchar *path; }; /* Signals */ /*enum { PROTO NUM_SIGNALS }; static guint triggers_signals[NUM_SIGNALS] = {0};*/ G_DEFINE_TYPE(GmTriggers, gm_triggers, G_TYPE_OBJECT) static void gm_triggers_finalize(GObject *object) { GmTriggers *trg = GM_TRIGGERS(object); GList *item; for (item = trg->priv->triggers; item; item = item->next) { gm_trigger_free((GmTrigger *)(item->data)); } g_free(trg->priv->path); g_list_free(trg->priv->triggers); G_OBJECT_CLASS(gm_triggers_parent_class)->finalize(object); } static void gm_triggers_class_init(GmTriggersClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = gm_triggers_finalize; g_type_class_add_private(object_class, sizeof(GmTriggersPrivate)); } static void gm_triggers_init(GmTriggers *trg) { trg->priv = GM_TRIGGERS_GET_PRIVATE(trg); trg->priv->triggers = NULL; trg->priv->path = NULL; } GmTrigger * gm_trigger_new() { return g_new0(GmTrigger, 1); } GList * gm_trigger_list_dup(GList *list) { GList *result = NULL; GList *item; GmTriggerData *data; for (item = list; item; item = item->next) { data = (GmTriggerData *)(item->data); result = g_list_append(result, gm_trigger_data_new(data->type, g_strdup(data->data))); } return result; } GmTrigger * gm_trigger_dup(GmTrigger *source) { GmTrigger *result = gm_trigger_new(); result->name = g_strdup(source->name); result->event = source->event; result->conditions = gm_trigger_list_dup(source->conditions); result->actions = gm_trigger_list_dup(source->actions); return result; } void gm_trigger_set_name(GmTrigger *trigger, const gchar *name) { g_free(trigger->name); trigger->name = g_strdup(name); } void gm_trigger_free_list(GList *list) { GList *item; GmTriggerData *data; for (item = list; item; item = item->next) { data = (GmTriggerData *)(item->data); gm_trigger_data_free(data); } g_list_free(list); } void gm_trigger_free(GmTrigger *trigger) { gm_trigger_free_list(trigger->conditions); gm_trigger_free_list(trigger->actions); g_free(trigger->name); g_free(trigger); } GmTriggerData * gm_trigger_data_new(gint type, gchar *data) { GmTriggerData *tdata = g_new0(GmTriggerData, 1); tdata->type = type; tdata->data = data; memset(&(tdata->expr), 0, sizeof(regex_t)); switch (tdata->type) { case TCT_MATCHES: case TCT_NOT_MATCHES: case TCT_USER_ONLINE: case TCT_USER_OFFLINE: case TCT_USER_IDLE: case TCT_USER_IDLE_OFF: case TCT_USER_AWAY: case TCT_USER_AWAY_OFF: regcomp(&(tdata->expr), tdata->data, REG_EXTENDED); break; default: break; } return tdata; } void gm_trigger_data_free(GmTriggerData *tdata) { g_free(tdata->data); g_free(tdata); } void gm_trigger_add_condition(GmTrigger *trigger, GmTriggerData *condition) { trigger->conditions = g_list_append(trigger->conditions, condition); } void gm_trigger_add_action(GmTrigger *trigger, GmTriggerData *action) { trigger->actions = g_list_append(trigger->actions, action); } void gm_trigger_set_conditions(GmTrigger *trigger, GList *conditions) { gm_trigger_free_list(trigger->conditions); trigger->conditions = conditions; } void gm_trigger_set_actions(GmTrigger *trigger, GList *actions) { gm_trigger_free_list(trigger->actions); trigger->actions = actions; } gint gm_trigger_match_user(GmTrigger *trigger, gchar const *username, GmTriggerConditionType condition, regmatch_t *matches, gint nmatch) { GmTriggerData *data; GList *item; gint cmatch = 0, offset, ret; if (!trigger->conditions) { return FALSE; } for (item = trigger->conditions; item; item = item->next) { data = (GmTriggerData *)(item->data); if (cmatch >= nmatch) { return 0; } if (data->type == condition) { offset = 0; ret = regexec(&(data->expr), (char *)(username), (nmatch - cmatch) - 1, matches + cmatch, 0); if (ret == 0) { while (matches[cmatch].rm_so != -1) { matches[cmatch].rm_eo = matches[cmatch].rm_eo - matches[cmatch].rm_so; matches[cmatch].rm_so = matches[cmatch].rm_so + offset; cmatch++; } } else { return 0; } } else { return 0; } } return cmatch; } gint gm_trigger_match(GmTrigger *trigger, gchar *text, regmatch_t *matches, gint nmatch) { GmTriggerData *data; GList *item; gchar *tmp; gboolean istrue; gint cmatch = 0, ret, offset, len; if (!trigger->conditions) { return 0; } for (item = trigger->conditions; item; item = item->next) { data = (GmTriggerData *)(item->data); len = g_utf8_strlen(data->data, -1); if (cmatch >= nmatch) { return 0; } switch (data->type) { case TCT_CONTAINS: case TCT_NOT_CONTAINS: case TCT_BEGINS: case TCT_NOT_BEGINS: case TCT_ENDS: case TCT_NOT_ENDS: tmp = strstr(text, data->data); switch (data->type) { case TCT_CONTAINS: if (tmp != NULL) { while (tmp != NULL && cmatch < nmatch) { matches[cmatch].rm_so = g_utf8_pointer_to_offset(text, tmp); matches[cmatch].rm_eo = len; tmp = strstr(tmp + len, data->data); if (tmp != NULL) { ++cmatch; } } } else { return 0; } break; case TCT_NOT_CONTAINS: if (tmp == NULL) { matches[cmatch].rm_so = 0; matches[cmatch].rm_eo = len; } else { return 0; } break; case TCT_NOT_BEGINS: if (tmp != text) { matches[cmatch].rm_so = 0; matches[cmatch].rm_eo = g_utf8_strlen(text, -1); } else { return 0; } break; default: istrue = (tmp != NULL && (g_utf8_pointer_to_offset(text, tmp) + len) == g_utf8_strlen(text, -1)); if (istrue && data->type == TCT_ENDS) { matches[cmatch].rm_so = g_utf8_pointer_to_offset(text, tmp); matches[cmatch].rm_eo = len; } else if (!istrue && data->type == TCT_NOT_ENDS) { matches[cmatch].rm_so = 0; matches[cmatch].rm_eo = g_utf8_strlen(text, -1); } else { return 0; } } cmatch++; matches[cmatch].rm_so = -1; break; case TCT_MATCHES: case TCT_NOT_MATCHES: offset = 0; ret = regexec(&(data->expr), (char *)text, (nmatch - cmatch) - 1, matches + cmatch, 0); if (ret == 0 && data->type == TCT_MATCHES) { while (ret == 0) { while (matches[cmatch].rm_so != -1) { matches[cmatch].rm_eo = matches[cmatch].rm_eo - matches[cmatch].rm_so; matches[cmatch].rm_so = matches[cmatch].rm_so + offset; ++cmatch; } offset = matches[cmatch - 1].rm_so + matches[cmatch - 1].rm_eo; if (cmatch < nmatch) { ret = regexec(&(data->expr), (char *)(text + offset), (nmatch - cmatch) - 1, matches + cmatch, 0); } else { ret = 1; } } } else if (ret != 0 && data->type == TCT_NOT_MATCHES) { matches[cmatch].rm_so = 0; matches[cmatch].rm_eo = g_utf8_strlen(text, -1); } else { return 0; } break; default: return 0; break; } } return cmatch; } gint gm_trigger_type_from_name(const gchar *name, const trigger_trans *trans_table) { int i; for (i = 0; trans_table[i].type != -1; i++) { if (strcasecmp(trans_table[i].name, name) == 0) { return trans_table[i].type; } } return -1; } const gchar * gm_trigger_name_from_type(gint type, const trigger_trans *trans_table) { int i; for (i = 0; trans_table[i].type != -1; i++) { if (trans_table[i].type == type) { return trans_table[i].name; } } return NULL; } void gm_triggers_parse_trigger(GmTriggers *trg, xmlDocPtr doc, xmlNodePtr node) { GmTrigger *result = gm_trigger_new(); xmlChar *tmp, *tmp2; gint type; tmp = xmlGetProp(node, (const xmlChar *)"name"); result->name = (gchar *)tmp; tmp = xmlGetProp(node, (const xmlChar *)"event"); if (xmlStrcmp(tmp, (const xmlChar *)("world")) == 0) { result->event = TT_OUTPUT; } else if (xmlStrcmp(tmp, (const xmlChar *)("player")) == 0){ result->event = TT_USERS; } else { xmlFree(tmp); gm_trigger_free(result); return; } xmlFree(tmp); for (node = node->xmlChildrenNode; node; node = node->next) { tmp = xmlGetProp(node, (const xmlChar *)"type"); tmp2 = xmlGetProp(node, (const xmlChar *)"data"); if (xmlStrcmp(node->name, (const xmlChar *)("condition")) == 0) { type = gm_trigger_type_from_name((const gchar *)(tmp), table_conditions); if (type != -1) { gm_trigger_add_condition(result, gm_trigger_data_new(type, g_strdup((char *)tmp2))); } } else if (xmlStrcmp(node->name, (const xmlChar *)("action")) == 0) { type = gm_trigger_type_from_name((const gchar *)(tmp), table_actions); if (type != -1) { gm_trigger_add_action(result, gm_trigger_data_new(type, g_strdup((char *)tmp2))); } } xmlFree(tmp2); xmlFree(tmp); } gm_triggers_add(trg, result); } GmTriggers * gm_triggers_new() { GmTriggers *trg = GM_TRIGGERS(g_object_new(GM_TYPE_TRIGGERS, NULL)); return trg; } void gm_triggers_add(GmTriggers *trg, GmTrigger *t) { trg->priv->triggers = g_list_append(trg->priv->triggers, t); } void gm_triggers_clear(GmTriggers *trg) { GList *item; for (item = trg->priv->triggers; item; item = item->next) { gm_trigger_free((GmTrigger *)(item->data)); } g_list_free(trg->priv->triggers); trg->priv->triggers = NULL; } GmTriggers * gm_triggers_new_from_file(gchar *filename) { GmTriggers *trg = GM_TRIGGERS(g_object_new(GM_TYPE_TRIGGERS, NULL)); xmlDocPtr doc; xmlNodePtr cur; trg->priv->path = g_strdup(filename); if (!g_file_test(filename, G_FILE_TEST_EXISTS)) { gm_debug_msg(DEBUG_DEFAULT, "GmTriggers.NewFromFile: Trigger file does not exist"); return trg; } doc = xmlParseFile(filename); if (doc == NULL) { gm_debug_msg(DEBUG_DEFAULT, "GmTriggers.NewFromFile: Error on parsing triggers"); return trg; } cur = xmlDocGetRootElement(doc); if (cur == NULL) { xmlFreeDoc(doc); return trg; } if (xmlStrcmp(cur->name, (const xmlChar *)("triggers"))) { gm_debug_msg(DEBUG_DEFAULT, "GmTriggers.NewFromFile: invalid root node"); xmlFreeDoc(doc); return trg; } for (cur = cur->xmlChildrenNode; cur; cur = cur->next) { if (!xmlStrcmp(cur->name, (const xmlChar *)("trigger"))) { gm_triggers_parse_trigger(trg, doc, cur); } } xmlFreeDoc(doc); return trg; } void gm_triggers_set_path(GmTriggers *trg, gchar *path) { g_free(trg->priv->path); trg->priv->path = g_strdup(path); } void gm_trigger_rules_xml(GmTrigger *t, xmlNodePtr trig) { GmTriggerData *data; GList *item; xmlNodePtr child; for (item = t->conditions; item; item = item->next) { data = (GmTriggerData *)(item->data); child = xmlNewChild(trig, NULL, (const xmlChar *)("condition"), NULL); xmlNewProp(child, (const xmlChar *)("type"), (const xmlChar *) (gm_trigger_name_from_type(data->type, table_conditions))); if (data->data) { xmlNewProp(child, (const xmlChar *)("data"), (const xmlChar *) (data->data)); } } for (item = t->actions; item; item = item->next) { data = (GmTriggerData *)(item->data); child = xmlNewChild(trig, NULL, (const xmlChar *)("action"), NULL); xmlNewProp(child, (const xmlChar *)("type"), (const xmlChar *) (gm_trigger_name_from_type(data->type, table_actions))); if (data->data) { xmlNewProp(child, (const xmlChar *)("data"), (const xmlChar *) (data->data)); } } } void gm_triggers_save(GmTriggers *trg) { xmlDocPtr doc; xmlNodePtr root; xmlNodePtr trig; GList *item; GmTrigger *t; g_return_if_fail(trg->priv->path != NULL); doc = xmlNewDoc((const xmlChar *)("1.0")); root = xmlNewNode(NULL, (const xmlChar *)("triggers")); xmlDocSetRootElement(doc, root); for (item = trg->priv->triggers; item; item = item->next) { t = (GmTrigger *)(item->data); trig = xmlNewChild(root, NULL, (const xmlChar *)("trigger"), NULL); xmlNewProp(trig, (const xmlChar *)("name"), (const xmlChar *)(t->name)); if (t->event == TT_OUTPUT) { xmlNewProp(trig, (const xmlChar *)("event"), (const xmlChar *)("world")); } else { xmlNewProp(trig, (const xmlChar *)("event"), (const xmlChar *)("player")); } gm_trigger_rules_xml(t, trig); } xmlSaveFormatFileEnc(trg->priv->path, doc, "UTF-8", 1); xmlFreeDoc(doc); } void gm_triggers_save_as(GmTriggers *trg, const gchar *path) { g_free(trg->priv->path); trg->priv->path = g_strdup(path); gm_triggers_save(trg); } GmTriggers * gm_triggers_dup(GmTriggers *source) { GmTriggers *trg = GM_TRIGGERS(g_object_new(GM_TYPE_TRIGGERS, NULL)); GList *item; for (item = source->priv->triggers; item; item = item->next) { trg->priv->triggers = g_list_append(trg->priv->triggers, gm_trigger_dup((GmTrigger *)(item->data))); } return trg; } const GList * gm_triggers_list(GmTriggers *trg) { return trg->priv->triggers; }