diff options
author | Anton Luka Šijanec <anton@sijanec.eu> | 2022-11-10 23:52:13 +0100 |
---|---|---|
committer | Anton Luka Šijanec <anton@sijanec.eu> | 2022-11-10 23:52:13 +0100 |
commit | 2f25b5ed4c131d594419e5c291d7f4ae869d8347 (patch) | |
tree | d2bb19ee801e7245007e7a6bcc202aa1e6bf3063 | |
download | bicikelj-stat-2f25b5ed4c131d594419e5c291d7f4ae869d8347.tar bicikelj-stat-2f25b5ed4c131d594419e5c291d7f4ae869d8347.tar.gz bicikelj-stat-2f25b5ed4c131d594419e5c291d7f4ae869d8347.tar.bz2 bicikelj-stat-2f25b5ed4c131d594419e5c291d7f4ae869d8347.tar.lz bicikelj-stat-2f25b5ed4c131d594419e5c291d7f4ae869d8347.tar.xz bicikelj-stat-2f25b5ed4c131d594419e5c291d7f4ae869d8347.tar.zst bicikelj-stat-2f25b5ed4c131d594419e5c291d7f4ae869d8347.zip |
-rw-r--r-- | .gitignore | 3 | ||||
-rw-r--r-- | d.c | 171 | ||||
-rw-r--r-- | makefile | 4 |
3 files changed, 178 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..208d9ce --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +d +db +core @@ -0,0 +1,171 @@ +#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; +} diff --git a/makefile b/makefile new file mode 100644 index 0000000..f5eb442 --- /dev/null +++ b/makefile @@ -0,0 +1,4 @@ +default: + gcc -Wextra -Wformat-security -Wall -pedantic -O0 -g d.c -od -lcurl -lcjson +run: + ./d >> db < db |