Files
archived-lcd4linux/plugin_kvv.c
michael 68bf671bff initialize plugin on first use
git-svn-id: https://ssl.bulix.org/svn/lcd4linux/trunk@944 3ae390bd-cb1e-0410-b409-cd5a39f66f1f
2009-01-06 06:46:50 +00:00

815 lines
18 KiB
C

/* $Id$
* $URL$
*
* plugin kvv (karlsruher verkehrsverbund)
*
* Copyright (C) 2006 Till Harbaum <till@harbaum.org>
* Copyright (C) 2006 The LCD4Linux Team <lcd4linux-devel@users.sourceforge.net>
*
* 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 <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <signal.h>
/* network specific includes */
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>
/* 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 <td> 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);
}