From 2e871a2380f375f81b137a659e6ffa0d8199cb83 Mon Sep 17 00:00:00 2001 From: harbaum <> Date: Sun, 13 Aug 2006 18:14:03 +0000 Subject: [lcd4linux @ 2006-08-13 18:14:03 by harbaum] Added KVV plugin --- plugin_kvv.c | 714 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 714 insertions(+) create mode 100644 plugin_kvv.c (limited to 'plugin_kvv.c') diff --git a/plugin_kvv.c b/plugin_kvv.c new file mode 100644 index 0000000..9db6674 --- /dev/null +++ b/plugin_kvv.c @@ -0,0 +1,714 @@ +/* $Id: plugin_kvv.c,v 1.1 2006/08/13 18:14:03 harbaum Exp $ + * + * plugin kvv (karlsruher verkehrsverbund) + * + * Copyright (C) 2006 Till Harbaum + * Copyright (C) 2006 The LCD4Linux Team + * + * This file is part of LCD4Linux. + * + * LCD4Linux is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * LCD4Linux is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * + * $Log: plugin_kvv.c,v $ + * Revision 1.1 2006/08/13 18:14:03 harbaum + * Added KVV plugin + * + * + */ + +/* + * exported functions: + * + * int plugin_init_kvv (void) + * adds various functions + * void plugin_exit_kvv (void) + * + */ + +/* define the include files you need */ +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include + +/* network specific includes */ +#include +#include +#include +#include +#include + +/* these should always be included */ +#include "debug.h" +#include "plugin.h" +#include "cfg.h" +#include "thread.h" + +/* these can't be configured as it doesn't make sense to change them */ +#define HTTP_SERVER "www.init-ka.de" +#define HTTP_REQUEST "/webfgi/StopInfoInplace.aspx?ID=%s" + +#define DEFAULT_STATION_ID "89" // Hauptbahnhof + +/* example ids: + * 89 = Hauptbahnhof + * 12_701 = Berufsakademie + */ + +/* total max values to calculate shm size */ +#define MAX_LINES 4 +#define MAX_LINE_LENGTH 8 +#define MAX_STATION_LENGTH 32 + +typedef struct { + char line[MAX_LINE_LENGTH + 1]; + char station[MAX_STATION_LENGTH + 1]; + int time; +} kvv_entry_t; + +typedef struct { + int entries; + kvv_entry_t entry[MAX_LINES]; +} kvv_shm_t; + +static char *station_id = NULL; +static char *proxy_name = NULL; +static int port = 80; +static pid_t pid = -1; +static int refresh = 60; +static int stringlen = 16; + +static int mutex = 0; +static int shmid; +static kvv_shm_t *shm; + +#define SECTION "Plugin:KVV" + +#define TIMEOUT_SHORT 1 /* wait this long for additional data */ +#define TIMEOUT_LONG 10 /* wait this long for initial data */ + +/* search an element in the result string */ +static int get_element(char *input, char *name, char **data) +{ + int skip = 0; + int len = 0; + int state = 0; // nothing found yet + + // search entire string + while (*input) { + if (skip == 0) { + switch (state) { + case 0: + if (*input == '<') + state = 1; + else + state = 0; + break; + + case 1: + // ignore white spaces + if (*input != ' ') { + if (strncasecmp(input, name, strlen(name)) == 0) { + state = 2; + skip = strlen(name) - 1; + } else + state = 0; + } + break; + + case 2: + if (*input == ' ') { + *data = ++input; + while (*input++ != '>') + len++; + + return len; + } else + state = 0; + break; + } + } else if (skip) + skip--; + + input++; + } + return -1; +} + +/* serach an attribute within an element */ +static int get_attrib(char *input, char *name, char **data) +{ + int skip = 0; + int len = 0; + int state = 0; // nothing found + + // search in this element + while (*input != '>') { + // ignore white spaces + if (((*input != ' ') && (*input != '\t')) && (skip == 0)) { + switch (state) { + case 0: + if (strncasecmp(input, name, strlen(name)) == 0) { + state = 1; + skip = strlen(name) - 1; + } + break; + + case 1: + if (*input == '=') + state = 2; + else + state = 0; + break; + + case 2: + if (*input == '\"') { + *data = ++input; + while (*input++ != '\"') + len++; + + return len; + } else + state = 0; + + break; + } + } else if (skip) + skip--; + + input++; + } + return -1; +} + +static int http_open(char *name) +{ + struct sockaddr_in server; + struct hostent *host_info; + unsigned long addr; + int sock; + + /* create socket */ + sock = socket(PF_INET, SOCK_STREAM, 0); + if (sock < 0) { + perror("failed to create socket"); + return -1; + } + + /* Erzeuge die Socketadresse des Servers + * Sie besteht aus Typ, IP-Adresse und Portnummer */ + memset(&server, 0, sizeof(server)); + if ((addr = inet_addr(name)) != INADDR_NONE) { + memcpy((char *) &server.sin_addr, &addr, sizeof(addr)); + } else { + /* Wandle den Servernamen in eine IP-Adresse um */ + host_info = gethostbyname(name); + if (NULL == host_info) { + error("[KVV] Unknown server: %s", name); + return -1; + } + memcpy((char *) &server.sin_addr, host_info->h_addr, host_info->h_length); + } + + server.sin_family = AF_INET; + server.sin_port = htons(port); + + /* Baue die Verbindung zum Server auf */ + if (connect(sock, (struct sockaddr *) &server, sizeof(server)) < 0) { + perror("can't connect to server"); + return -1; + } + + return sock; +} + +static void get_text(char *input, char *end, char *dest, int dlen) +{ + int state = 0; // nothing yet, outside any element + int cnt = 0; + + while (*input) { + switch (state) { + case 0: + if (*input == '<') + state = 1; + else { + if (cnt < (dlen - 1)) + dest[cnt++] = *input; + } + break; + + case 1: + if (*input == '/') + state = 2; + else if (*input == '>') + state = 0; + break; + + case 2: + if (strncasecmp(input, end, strlen(end)) == 0) { + dest[cnt++] = 0; + return; + } + break; + } + + input++; + } + +} + +static void process_station_string(char *str) +{ + char *p, *q; + int last, i; + char *repl[] = { + "Hauptbahnhof", "Hbf.", + "Bahnhof", "Bhf.", + "Karlsruhe", "KA", + "Schienenersatzverkehr", "Ersatzv.", + "Marktplatz", "Marktpl.", + }; + + /* erase multiple spaces */ + p = q = str; + last = 1; // no leading spaces + while (*p) { + if ((!last) || (*p != ' ')) + *q++ = *p; + + last = (*p == ' '); + p++; + } + *q++ = 0; + + /* decode utf8 */ + p = q = str; + last = 0; + while (*p) { + if (last) { + *q++ = (last << 6) | (*p & 0x3f); + last = 0; + } else if ((*p & 0xe0) == 0xc0) { + last = *p & 3; + } else + *q++ = *p; + + p++; + } + *q++ = 0; + + /* replace certain (long) words with e.g. abbreviations */ + for (i = 0; i < (int) (sizeof(repl) / (2 * sizeof(char *))); i++) { + if ((p = strstr(str, repl[2 * i])) != NULL) { + + /* move new string */ + memcpy(p, repl[2 * i + 1], strlen(repl[2 * i + 1])); + /* move rest of string down */ + memmove(p + strlen(repl[2 * i + 1]), + p + strlen(repl[2 * i]), strlen(str) - (p - str) - strlen(repl[2 * i]) + 1); + } + } +} + +static void kvv_client(void) +{ + char ibuffer[8192]; + char obuffer[8192]; + int count, i, sock; + + char server_name[] = HTTP_SERVER; + char *connect_to; + + /* connect to proxy if given, to server otherwise */ + if ((proxy_name != NULL) && (strlen(proxy_name) != 0)) + connect_to = proxy_name; + else + connect_to = server_name; + + info("[KVV] Connecting to %s", connect_to); + + while (1) { + + sock = http_open(connect_to); + if (sock < 0) { + error("[KVV] Error accessing server/proxy: %s", strerror(errno)); + return; + } + // create and set get request + sprintf(obuffer, "GET http://%s" HTTP_REQUEST " HTTP/1.1\n" "Host: %s\n\n", server_name, station_id, + server_name); + + info("[KVV] Sending first (GET) request ..."); + send(sock, obuffer, strlen(obuffer), 0); + + count = 0; + do { + fd_set rfds; + struct timeval tv; + + FD_ZERO(&rfds); + FD_SET(sock, &rfds); + + tv.tv_sec = count ? TIMEOUT_SHORT : TIMEOUT_LONG; + tv.tv_usec = 0; + + i = select(FD_SETSIZE, &rfds, NULL, NULL, &tv); + if (i < 0) { + perror("select"); + exit(1); + } + + if (i != 0) { + i = recv(sock, ibuffer + count, sizeof(ibuffer) - count, 0); + count += i; + } + } + while (i > 0); + + ibuffer[count] = 0; // terminate string + close(sock); + + if (count > 0) { + char *input, *cookie, *name, *value; + int input_len, cookie_len, name_len, value_len; + + // buffer to html encode value + char value_enc[512]; + int value_enc_len; + + // find cookie + cookie_len = 0; + cookie = strstr(ibuffer, "Set-Cookie:"); + if (cookie) { + cookie += strlen("Set-Cookie:"); + + while (*cookie == ' ') + cookie++; + + while (cookie[cookie_len] != ';') + cookie_len++; + } + // find input element + input_len = get_element(ibuffer, "input", &input); + + + if (input_len > 0) { + char *input_end = input; + while (*input_end != '>') + input_end++; + while (*input_end != '\"') + input_end--; + *(input_end + 1) = 0; + + name_len = get_attrib(input, "name", &name); + value_len = get_attrib(input, "value", &value); + + for (value_enc_len = 0, i = 0; i < value_len; i++) { + if (isalnum(value[i])) + value_enc[value_enc_len++] = value[i]; + else { + sprintf(value_enc + value_enc_len, "%%%02X", 0xff & value[i]); + value_enc_len += 3; + } + } + + if (cookie_len >= 0) + cookie[cookie_len] = 0; + if (name_len >= 0) + name[name_len] = 0; + if (value_len >= 0) + value[value_len] = 0; + if (value_enc_len >= 0) + value_enc[value_enc_len] = 0; + + sock = http_open(connect_to); + + // send POST + sprintf(obuffer, + "POST http://%s" HTTP_REQUEST " HTTP/1.1\n" + "Host: %s\n" + "Cookie: %s\n" + "Content-Type: application/x-www-form-urlencoded\n" + "Content-Length: %d\n" + "\n%s=%s", + server_name, station_id, server_name, cookie, name_len + value_enc_len + 1, name, value_enc); + + info("[KVV] Sending second (POST) request ..."); + send(sock, obuffer, strlen(obuffer), 0); + + count = 0; + do { + fd_set rfds; + struct timeval tv; + + FD_ZERO(&rfds); + FD_SET(sock, &rfds); + + tv.tv_sec = count ? TIMEOUT_SHORT : TIMEOUT_LONG; + tv.tv_usec = 0; + + i = select(FD_SETSIZE, &rfds, NULL, NULL, &tv); + if (i > 0) { + i = recv(sock, ibuffer + count, sizeof(ibuffer) - count, 0); + count += i; + } + } + while (i > 0); /* leave on select or read error */ + + ibuffer[count] = 0; + + /* close connection */ + close(sock); + + if (count > 0) { + int last_was_stop = 0; + char *td = ibuffer; + char str[32]; + int td_len, i, overflow = 0; + + /* lock shared memory */ + mutex_lock(mutex); + + /* free allocated memory */ + shm->entries = 0; + + /* scan through all entries and search the line nums */ + do { + if ((td_len = get_element(td, "td", &td)) > 0) { + char *attr, *p; + int attr_len; + + /* time does not have a class but comes immediately after stop :-( */ + if (last_was_stop) { + td += td_len + 1; + get_text(td, "td", str, sizeof(str)); + + /* time needs special treatment */ + if (strncasecmp(str, "sofort", strlen("sofort")) == 0) + i = 0; + else { + /* skip everything that is not a number */ + p = str; + while (!isdigit(*p)) + p++; + + /* and convert remaining to number */ + i = atoi(p); + } + + /* save time */ + if (!overflow) + shm->entry[shm->entries - 1].time = i; + + last_was_stop = 0; + } + + /* linenum and stopname fields have proper classes */ + if ((attr_len = get_attrib(td, "class", &attr)) > 0) { + + if (strncasecmp(attr, "lineNum", strlen("lineNum")) == 0) { + td += td_len + 1; + get_text(td, "td", str, sizeof(str)); + + if (shm->entries < MAX_LINES) { + // allocate a new slot + shm->entries++; + shm->entry[shm->entries - 1].time = -1; + memset(shm->entry[shm->entries - 1].line, 0, MAX_LINE_LENGTH + 1); + memset(shm->entry[shm->entries - 1].station, 0, MAX_STATION_LENGTH + 1); + + // add new lines entry + strncpy(shm->entry[shm->entries - 1].line, str, MAX_LINE_LENGTH); + } else + overflow = 1; /* don't add further entries */ + } + + if (strncasecmp(attr, "stopname", strlen("stopname")) == 0) { + td += td_len + 1; + get_text(td, "td", str, sizeof(str)); + + /* stopname may need further tuning */ + process_station_string(str); + + if (!overflow) + strncpy(shm->entry[shm->entries - 1].station, str, MAX_STATION_LENGTH); + + last_was_stop = 1; + } + } + } + } while (td_len >= 0); + + mutex_unlock(mutex); + } + } + } + + sleep(refresh); + } +} + +static void kvv_line(RESULT * result, RESULT * arg1) +{ + int index = (int) R2N(arg1); + + mutex_lock(mutex); + + if (index < shm->entries) { + SetResult(&result, R_STRING, shm->entry[index].line); + } else + SetResult(&result, R_STRING, "---"); + + mutex_unlock(mutex); +} + +static void kvv_station(RESULT * result, RESULT * arg1) +{ + int index = (int) R2N(arg1); + + mutex_lock(mutex); + + if (index < shm->entries) + SetResult(&result, R_STRING, shm->entry[index].station); + else + SetResult(&result, R_STRING, "---"); + + mutex_unlock(mutex); +} + +static void kvv_time(RESULT * result, RESULT * arg1) +{ + int index = (int) R2N(arg1); + double value = -1.0; + + mutex_lock(mutex); + + if (index < shm->entries) + value = shm->entry[index].time; + + SetResult(&result, R_NUMBER, &value); + + mutex_unlock(mutex); +} + +static void kvv_time_str(RESULT * result, RESULT * arg1) +{ + int index = (int) R2N(arg1); + + mutex_lock(mutex); + + if (index < shm->entries) { + char str[8]; + sprintf(str, "%d", shm->entry[index].time); + SetResult(&result, R_STRING, str); + } else + SetResult(&result, R_STRING, "---"); + + mutex_unlock(mutex); +} + +/* plugin initialization */ +int plugin_init_kvv(void) +{ + int val; + char *p; + + /* register all our cool functions */ + AddFunction("kvv::line", 1, kvv_line); + AddFunction("kvv::station", 1, kvv_station); + AddFunction("kvv::time", 1, kvv_time); + AddFunction("kvv::time_str", 1, kvv_time_str); + + /* parse parameter */ + if ((p = cfg_get(SECTION, "StationID", DEFAULT_STATION_ID)) != NULL) { + station_id = malloc(strlen(p) + 1); + strcpy(station_id, p); + } + info("[KVV] Using station %s", station_id); + + if ((p = cfg_get(SECTION, "Proxy", NULL)) != NULL) { + proxy_name = malloc(strlen(p) + 1); + strcpy(proxy_name, p); + info("[KVV] Using proxy \"%s\"", proxy_name); + } + + if (cfg_number(SECTION, "Port", 0, 0, 65535, &val) > 0) { + port = val; + info("[KVV] Using port %d", port); + } else { + info("[KVV] Using default port %d", port); + } + + if (cfg_number(SECTION, "Refresh", 0, 0, 65535, &val) > 0) { + refresh = val; + info("[KVV] Using %d seconds refresh interval", refresh); + } else { + info("[KVV] Using default refresh interval of %d seconds", refresh); + } + + if (cfg_number(SECTION, "Stringlen", 0, 0, 65535, &val) > 0) { + stringlen = val; + info("[KVV] Using %d character string length", stringlen); + } else { + info("[KVV] Using %d characters default string length", stringlen); + } + + /* create communication buffer */ + shmid = shm_create((void **) &shm, sizeof(kvv_shm_t)); + + /* catch error */ + if (shmid < 0) { + error("[KVV] Shared memory allocation failed!"); + return -1; + } + + /* attach client thread */ + mutex = mutex_create(); + pid = fork(); + if (pid < 0) { + error("[KVV] Unable to fork client: %s", strerror(errno)); + return -1; + } + + if (pid == 0) + kvv_client(); + else + info("[KVV] forked client with pid %d", pid); + + return 0; +} + +void plugin_exit_kvv(void) +{ + if (pid != -1) + kill(pid, SIGTERM); + + if (station_id) + free(station_id); + if (proxy_name) + free(proxy_name); + + mutex_destroy(mutex); + + if (shm) + shm_destroy(shmid, shm); +} -- cgit v1.2.3