From 181cec4348da40331b3e8ab365732c025ec149b2 Mon Sep 17 00:00:00 2001 From: Reinhard Tartler Date: Wed, 27 Apr 2011 19:24:15 +0200 Subject: Import upstream version 0.11.0~svn1143 --- plugin_kvv.c | 814 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 814 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..f0950d3 --- /dev/null +++ b/plugin_kvv.c @@ -0,0 +1,814 @@ +/* $Id: plugin_kvv.c 944 2009-01-06 06:46:50Z michael $ + * $URL: https://ssl.bulix.org/svn/lcd4linux/trunk/plugin_kvv.c $ + * + * 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. + * + */ + +/* + * 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 USER_AGENT "lcd4linux - KVV plugin (http://ssl.bulix.org/projects/lcd4linux/wiki/plugin_kvv)" + +#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 40 + +typedef struct { + char line[MAX_LINE_LENGTH + 1]; + char station[MAX_STATION_LENGTH + 1]; + int time; +} kvv_entry_t; + +typedef struct { + int entries, error; + 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 abbreviate = 0; + +static int initialized = 0; +static int mutex = 0; +static int shmid = -1; +static kvv_shm_t *shm = NULL; + +#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 && (*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; + + /* some strings to replace */ + char *repl[] = { + "Hauptbahnhof", "Hbf.", + "Bahnhof", "Bhf.", + "Karlsruhe", "KA", + "Schienenersatzverkehr", "Ersatzv.", + "Marktplatz", "Marktpl.", + }; + + /* 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; + + /* erase multiple spaces and replace umlauts */ + p = q = str; + last = 1; /* no leading spaces */ + while (*p) { + if ((!last) || (*p != ' ')) { + + /* translate from latin1 to hd44780 */ + if (*p == (char) 228) /* lower a umlaut */ + *q++ = (char) 0xe1; + else if (*p == (char) 223) /* sz ligature */ + *q++ = (char) 0xe2; + else if (*p == (char) 246) /* lower o umlaut */ + *q++ = (char) 0xef; + else if (*p == (char) 252) /* lower u umlaut */ + *q++ = (char) 0xf5; + else + *q++ = *p; + } + + last = (*p == ' '); + p++; + } + *q++ = 0; + + /* replace certain (long) words with e.g. abbreviations if enabled */ + if (abbreviate) { + + 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( __attribute__ ((unused)) + void *dummy) +{ + char ibuffer[8192]; + char obuffer[1024]; + 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 */ + if (snprintf(obuffer, sizeof(obuffer), + "GET http://%s" HTTP_REQUEST " HTTP/1.1\n" + "Host: %s\n" "User-Agent: " USER_AGENT "\n\n", server_name, station_id, + server_name) >= (int) sizeof(obuffer)) { + + info("[KVV] Warning, request has been truncated!"); + } + + 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 - 1, 0); + count += i; + } + } + while (i > 0); + + ibuffer[count] = 0; /* terminate string */ + close(sock); + + if (!count) + info("[KVV] empty/no reply"); + + if (count > 0) { + char *input, *cookie, *name = NULL, *value = NULL; + 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 */ + if (snprintf(obuffer, sizeof(obuffer), + "POST http://%s" HTTP_REQUEST " HTTP/1.1\n" + "Host: %s\n" + "User-Agent: " USER_AGENT "\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) >= (int) sizeof(obuffer)) { + + info("[KVV] Warning, request has been truncated!"); + } + + 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 - 1, 0); + count += i; + } + } + while (i > 0); /* leave on select or read error */ + + ibuffer[count] = 0; + + /* printf("Result (%d):\n%s\n", count, ibuffer); */ + + /* close connection */ + close(sock); + + if (!count) + info("[KVV] empty/no reply"); + + 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; + + if (strstr(ibuffer, "Die Daten konnten nicht abgefragt werden.") != NULL) { + info("[KVV] Server returned error!"); + /* printf("%s\n", ibuffer); */ + shm->error = 1; + } else + shm->error = 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 int kvv_fork(void) +{ + if (initialized) + return 0; + + info("[KVV] creating client thread"); + + /* set this here to prevent continous retries if init fails */ + initialized = 1; + + /* 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 = thread_create("plugin_kvv", kvv_client, NULL); + + if (pid < 0) { + error("[KVV] Unable to fork client: %s", strerror(errno)); + return -1; + } + + info("[KVV] forked client with pid %d", pid); + return 0; +} + +static void kvv_start(void) +{ + static int started = 0; + int val; + char *p; + + + if (started) + return; + + started = 1; + + /* 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, "Abbreviate", 0, 0, 65535, &val) > 0) { + abbreviate = val; + info("[KVV] Abbreviation enabled: %s", abbreviate ? "on" : "off"); + } else { + info("[KVV] Default abbreviation setting: %s", abbreviate ? "on" : "off"); + } + +} + +static void kvv_line(RESULT * result, RESULT * arg1) +{ + int index = (int) R2N(arg1); + + kvv_start(); + + if (kvv_fork() != 0) { + SetResult(&result, R_STRING, ""); + return; + } + + 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); + + kvv_start(); + + if (kvv_fork() != 0) { + SetResult(&result, R_STRING, ""); + return; + } + + mutex_lock(mutex); + + if (shm->error && index == 0) + SetResult(&result, R_STRING, "Server Err"); + else { + 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; + + kvv_start(); + + if (kvv_fork() != 0) { + SetResult(&result, R_STRING, ""); + return; + } + + 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); + + kvv_start(); + + if (kvv_fork() != 0) { + SetResult(&result, R_STRING, ""); + return; + } + + 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) +{ + /* 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); + return 0; +} + +void plugin_exit_kvv(void) +{ + /* kill client thread if it's running */ + if (initialized) { + /* kill client */ + if (pid != -1) + thread_destroy(pid); + + /* free shared mem and its mutex */ + if (shm) { + shm_destroy(shmid, shm); + mutex_destroy(mutex); + } + } + + if (station_id) + free(station_id); + if (proxy_name) + free(proxy_name); +} -- cgit v1.2.3