summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAnton Luka Šijanec <anton@sijanec.eu>2022-11-10 23:52:13 +0100
committerAnton Luka Šijanec <anton@sijanec.eu>2022-11-10 23:52:13 +0100
commit2f25b5ed4c131d594419e5c291d7f4ae869d8347 (patch)
treed2bb19ee801e7245007e7a6bcc202aa1e6bf3063
downloadbicikelj-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--.gitignore3
-rw-r--r--d.c171
-rw-r--r--makefile4
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
diff --git a/d.c b/d.c
new file mode 100644
index 0000000..eefdece
--- /dev/null
+++ b/d.c
@@ -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