#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <time.h>
#include <string.h>
#include <sys/types.h>
#include <error.h>
#include <endian.h>
#include <inttypes.h>
#include <curl/curl.h>
#include <cjson/cJSON.h>
#define S0(x) (x ? x : "")
enum entry {
TALLY,
QUOTA
};
#define TYPE_SHIFT (8*7)
#define STATION_SHIFT (8*6)
#define VALUE_SHIFT (8*5)
#define TYPE_INS(x) ((uint64_t) x << TYPE_SHIFT)
#define STATION_INS(x) ((uint64_t) x << STATION_SHIFT)
#define VALUE_INS(x) ((uint64_t) x << VALUE_SHIFT)
#define TYPE_RET(x) ((x & TYPE_INS(0xFF)) >> TYPE_SHIFT)
#define STATION_RET(x) ((x & STATION_INS(0xFF)) >> STATION_SHIFT)
#define VALUE_RET(x) ((x & VALUE_INS(0xFF)) >> VALUE_SHIFT)
#define TIME 0xFFFFFFFF
char * response = NULL;
size_t response_len = 0;
size_t write_callback (char * ptr, size_t size, size_t nmemb, void * userdata __attribute__((unused))) {
nmemb *= size;
char * mem = realloc(response, response_len+nmemb+1);
if (!mem)
return 0;
response = mem;
strncpy(response+response_len, ptr, nmemb);
response[(response_len += nmemb)] = '\0';
return nmemb;
}
int main (int argc, char ** argv) {
cJSON * json = NULL;
int r = 0;
CURL * curl = NULL;
unsigned char tally[256];
unsigned char quota[256];
memset(tally, 255, 256);
memset(quota, 255, 256);
if (fseek(stdin, -8, SEEK_END) == -1) {
if (errno == EINVAL)
fprintf(stderr, "nova (prazna) podatkovna zbirka!\n");
else
error_at_line(1, errno, __FILE__, __LINE__, "fseek. uporaba: %s >> db < db", S0(argv[0]));
}
uint64_t entry;
while (fread(&entry, sizeof entry, 1, stdin)) {
entry = be64toh(entry);
char čas[256];
time_t time = entry & TIME;
strftime(čas, 256, "%c", localtime(&time));
switch (TYPE_RET(entry)) {
case TALLY:
if (tally[STATION_RET(entry)] == 255) {
tally[STATION_RET(entry)] = VALUE_RET(entry);
fprintf(stderr, "[db] %s: na postaji %" PRIu64 " je %" PRIu64 " koles\n", čas, STATION_RET(entry), VALUE_RET(entry));
}
break;
case QUOTA:
if (quota[STATION_RET(entry)] == 255) {
quota[STATION_RET(entry)] = VALUE_RET(entry);
fprintf(stderr, "[db] %s: na postaji %" PRIu64 " je %" PRIu64 " postajališč\n", čas, STATION_RET(entry), VALUE_RET(entry));
}
break;
default:
error_at_line(2, 0, __FILE__, __LINE__, "invalid entry with type %" PRIu64, TYPE_RET(entry));
}
if (fseek(stdin, -16, SEEK_CUR) == -1)
break;
}
char curlerr[CURL_ERROR_SIZE];
curl = curl_easy_init();
if (!curl)
error_at_line(2, 0, __FILE__, __LINE__, "!curl");
curl_easy_setopt(curl, CURLOPT_URL, "https://api.jcdecaux.com/vls/v3/stations?apiKey=frifk0jbxfefqqniqez09tw4jvk37wyf823b5j1i&contract=ljubljana");
curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, curlerr);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
char buf[256];
snprintf(buf, 255, "bicikelj-stat/0.0.0 sends at most one request per 50 seconds. contact by email: %s", argc > 1 ? argv[1] : "not provided");
curl_easy_setopt(curl, CURLOPT_USERAGENT, buf);
while (1) {
fprintf(stderr, ".");
curlerr[0] = '\0';
response_len = 0;
CURLcode res = curl_easy_perform(curl);
if (res != CURLE_OK) {
error_at_line(0, 0, __FILE__, __LINE__, "!= CURLE_OK: %s", curlerr[0] ? curlerr : curl_easy_strerror(res));
r = 3;
goto r;
}
json = cJSON_Parse(response);
cJSON * station = NULL;
cJSON_ArrayForEach(station, json) {
cJSON * number = cJSON_GetObjectItem(station, "number");
cJSON * name = cJSON_GetObjectItem(station, "name");
cJSON * capacity = cJSON_GetObjectItem(cJSON_GetObjectItem(station, "totalStands"), "capacity");
cJSON * bikes = cJSON_GetObjectItem(cJSON_GetObjectItem(cJSON_GetObjectItem(station, "totalStands"), "availabilities"), "bikes");
if (!cJSON_IsNumber(number)) {
error_at_line(0, 0, __FILE__, __LINE__, "number");
r = 4;
goto r;
}
if (!cJSON_IsNumber(capacity)) {
error_at_line(0, 0, __FILE__, __LINE__, "capacity");
r = 5;
goto r;
}
if (!cJSON_IsNumber(bikes)) {
error_at_line(0, 0, __FILE__, __LINE__, "bikes");
r = 6;
goto r;
}
if (!cJSON_IsString(name)) {
error_at_line(0, 0, __FILE__, __LINE__, "name");
r = 7;
goto r;
}
if (number->valueint >= 255 || capacity->valueint >= 255 || bikes->valueint >= 255) {
error_at_line(0, 0, __FILE__, __LINE__, "number || capacity || bikes >= 255: evil server!");
r = 8;
goto r;
}
char čas[256];
time_t tajm = entry = time(NULL);
strftime(čas, 256, "%c", localtime(&tajm));
if (quota[number->valueint] != capacity->valueint) {
unsigned old = quota[number->valueint];
entry |= TYPE_INS(QUOTA) | STATION_INS(number->valueint) | VALUE_INS(capacity->valueint);
if (old == 255)
fprintf(stderr, "%s: na postaji %s (%d) je %d postajališč\n", čas, name->valuestring, number->valueint, capacity->valueint);
else
fprintf(stderr, "%s: na postaji %s (%d) je %d postajališč (prej %u)\n", čas, name->valuestring, number->valueint, capacity->valueint, old);
quota[number->valueint] = capacity->valueint;
entry = htobe64(entry);
fwrite(&entry, sizeof entry, 1, stdout);
}
entry = time(NULL);
if (tally[number->valueint] != bikes->valueint) {
unsigned old = tally[number->valueint];
entry |= TYPE_INS(TALLY) | STATION_INS(number->valueint) | VALUE_INS(bikes->valueint);
if (old == 255)
fprintf(stderr, "%s: na postaji %s (%d) je %d koles\n", čas, name->valuestring, number->valueint, bikes->valueint);
else
fprintf(stderr, "%s: na postaji %s (%d) je %d koles (prej %u)\n", čas, name->valuestring, number->valueint, bikes->valueint, old);
tally[number->valueint] = bikes->valueint;
entry = htobe64(entry);
fwrite(&entry, sizeof entry, 1, stdout);
}
fflush(stdout);
}
cJSON_Delete(json);
json = NULL;
sleep(50);
}
r:
cJSON_Delete(json);
json = NULL;
curl_easy_cleanup(curl);
curl = NULL;
free(response);
return r;
}