mirror of
https://github.com/owntracks/recorder.git
synced 2026-02-13 20:49:51 +00:00
Initial import
This commit is contained in:
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
*.o
|
||||
ot-recorder
|
||||
ocat
|
||||
*.dSYM
|
||||
.DS_Store
|
||||
store/
|
||||
17
Makefile
Normal file
17
Makefile
Normal file
@@ -0,0 +1,17 @@
|
||||
CFLAGS=-Wall -Werror -g
|
||||
|
||||
all: ot-recorder #### ot-reader
|
||||
|
||||
ot-reader: ot-reader.c json.o utstring.h ghash.o mkpath.o jget.o
|
||||
cc $(CFLAGS) ot-reader.c -o ot-reader json.o ghash.o mkpath.o jget.o -lhiredis
|
||||
|
||||
ot-recorder: ot-recorder.c json.o utarray.h utstring.h geo.o geohash.o mkpath.o file.o safewrite.o base64.o ghash.o config.h udata.h
|
||||
cc $(CFLAGS) ot-recorder.c -o ot-recorder json.o geo.o geohash.o mkpath.o file.o safewrite.o base64.o ghash.o -lmosquitto -lcurl -lhiredis
|
||||
|
||||
geo.o: geo.h geo.c udata.h
|
||||
geohash.o: geohash.h geohash.c udata.h
|
||||
file.o: file.h file.c config.h
|
||||
base64.o: base64.h base64.c
|
||||
ghash.o: ghash.h ghash.c config.h udata.h
|
||||
safewrite.o: safewrite.h safewrite.c
|
||||
jget.o: jget.c jget.h json.h
|
||||
151
base64.c
Executable file
151
base64.c
Executable file
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* Copyright (c) 1995, 1996, 1997 Kungliga Tekniska Hgskolan
|
||||
* (Royal Institute of Technology, Stockholm, Sweden).
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* 3. All advertising materials mentioning features or use of this software
|
||||
* must display the following acknowledgement:
|
||||
* This product includes software developed by the Kungliga Tekniska
|
||||
* Hgskolan and its contributors.
|
||||
*
|
||||
* 4. Neither the name of the Institute nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
/*RCSID("$Id: base64.c,v 1.1 2005/02/11 07:34:35 jpm Exp jpm $");*/
|
||||
#endif
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "base64.h"
|
||||
|
||||
static char base64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
|
||||
static int pos(char c)
|
||||
{
|
||||
char *p;
|
||||
for(p = base64; *p; p++)
|
||||
if(*p == c)
|
||||
return p - base64;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int base64_encode(const void *data, int size, char **str)
|
||||
{
|
||||
char *s, *p;
|
||||
int i;
|
||||
int c;
|
||||
unsigned char *q;
|
||||
|
||||
p = s = (char*)malloc(size*4/3+4);
|
||||
if (p == NULL)
|
||||
return -1;
|
||||
q = (unsigned char*)data;
|
||||
i=0;
|
||||
for(i = 0; i < size;){
|
||||
c=q[i++];
|
||||
c*=256;
|
||||
if(i < size)
|
||||
c+=q[i];
|
||||
i++;
|
||||
c*=256;
|
||||
if(i < size)
|
||||
c+=q[i];
|
||||
i++;
|
||||
p[0]=base64[(c&0x00fc0000) >> 18];
|
||||
p[1]=base64[(c&0x0003f000) >> 12];
|
||||
p[2]=base64[(c&0x00000fc0) >> 6];
|
||||
p[3]=base64[(c&0x0000003f) >> 0];
|
||||
if(i > size)
|
||||
p[3]='=';
|
||||
if(i > size+1)
|
||||
p[2]='=';
|
||||
p+=4;
|
||||
}
|
||||
*p=0;
|
||||
*str = s;
|
||||
return strlen(s);
|
||||
}
|
||||
|
||||
int base64_decode(const char *str, void *data)
|
||||
{
|
||||
const char *p;
|
||||
unsigned char *q;
|
||||
int c;
|
||||
int x;
|
||||
int done = 0;
|
||||
q=(unsigned char*)data;
|
||||
for(p=str; *p && !done; p+=4){
|
||||
x = pos(p[0]);
|
||||
if(x >= 0)
|
||||
c = x;
|
||||
else{
|
||||
done = 3;
|
||||
break;
|
||||
}
|
||||
c*=64;
|
||||
|
||||
x = pos(p[1]);
|
||||
if(x >= 0)
|
||||
c += x;
|
||||
else
|
||||
return -1;
|
||||
c*=64;
|
||||
|
||||
if(p[2] == '=')
|
||||
done++;
|
||||
else{
|
||||
x = pos(p[2]);
|
||||
if(x >= 0)
|
||||
c += x;
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
c*=64;
|
||||
|
||||
if(p[3] == '=')
|
||||
done++;
|
||||
else{
|
||||
if(done)
|
||||
return -1;
|
||||
x = pos(p[3]);
|
||||
if(x >= 0)
|
||||
c += x;
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
if(done < 3)
|
||||
*q++=(c&0x00ff0000)>>16;
|
||||
|
||||
if(done < 2)
|
||||
*q++=(c&0x0000ff00)>>8;
|
||||
if(done < 1)
|
||||
*q++=(c&0x000000ff)>>0;
|
||||
}
|
||||
return q - (unsigned char*)data;
|
||||
}
|
||||
47
base64.h
Executable file
47
base64.h
Executable file
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright (c) 1995, 1996, 1997 Kungliga Tekniska Hgskolan
|
||||
* (Royal Institute of Technology, Stockholm, Sweden).
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* 3. All advertising materials mentioning features or use of this software
|
||||
* must display the following acknowledgement:
|
||||
* This product includes software developed by the Kungliga Tekniska
|
||||
* Hgskolan and its contributors.
|
||||
*
|
||||
* 4. Neither the name of the Institute nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
/* $Id: base64.h,v 1.1 2005/02/11 07:34:35 jpm Exp jpm $ */
|
||||
|
||||
#ifndef _BASE64_H_
|
||||
#define _BASE64_H_
|
||||
|
||||
int base64_encode(const void *data, int size, char **str);
|
||||
int base64_decode(const char *str, void *data);
|
||||
|
||||
#endif
|
||||
3
config.h
Normal file
3
config.h
Normal file
@@ -0,0 +1,3 @@
|
||||
#define HAVE_REDIS
|
||||
#define JSONDIR "store"
|
||||
int mkpath(char *path);
|
||||
104
file.c
Normal file
104
file.c
Normal file
@@ -0,0 +1,104 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <ctype.h>
|
||||
#include <time.h>
|
||||
#include "file.h"
|
||||
|
||||
static void ut_lower(UT_string *us)
|
||||
{
|
||||
char *p;
|
||||
|
||||
for (p = utstring_body(us); p && *p; p++) {
|
||||
if (!isalnum(*p) || isspace(*p))
|
||||
*p = '-';
|
||||
else if (isupper(*p))
|
||||
*p = tolower(*p);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
static void ut_clean(UT_string *us)
|
||||
{
|
||||
char *p;
|
||||
|
||||
for (p = utstring_body(us); p && *p; p++) {
|
||||
if (isspace(*p))
|
||||
*p = '-';
|
||||
}
|
||||
}
|
||||
|
||||
static const char *yyyymm(time_t t) {
|
||||
static char buf[] = "YYYY-MM";
|
||||
|
||||
strftime(buf, sizeof(buf), "%Y-%m", gmtime(&t));
|
||||
return(buf);
|
||||
}
|
||||
|
||||
/* Return an open append file pointer to storage for user/device,
|
||||
creating directories on the fly. If device is NULL, omit it.
|
||||
*/
|
||||
|
||||
FILE *pathn(char *mode, char *prefix, UT_string *user, UT_string *device, char *suffix)
|
||||
{
|
||||
static UT_string *path = NULL;
|
||||
time_t now;
|
||||
|
||||
utstring_renew(path);
|
||||
|
||||
ut_lower(user);
|
||||
|
||||
if (device) {
|
||||
ut_lower(device);
|
||||
}
|
||||
|
||||
//utstring_printf(path, "%s/%s/%-1.1s/%-2.2s",
|
||||
// JSONDIR, prefix, utstring_body(user), utstring_body(user));
|
||||
utstring_printf(path, "%s/%s/%s/%s", JSONDIR, prefix, utstring_body(user), utstring_body(device));
|
||||
|
||||
ut_clean(path);
|
||||
|
||||
if (mkpath(utstring_body(path)) < 0) {
|
||||
perror(utstring_body(path));
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
|
||||
#if 0
|
||||
if (device) {
|
||||
utstring_printf(path, "/%s-%s.%s",
|
||||
utstring_body(user), utstring_body(device), suffix);
|
||||
} else {
|
||||
utstring_printf(path, "/%s.%s",
|
||||
utstring_body(user), suffix);
|
||||
}
|
||||
#endif
|
||||
|
||||
time(&now);
|
||||
utstring_printf(path, "/%s.%s", yyyymm(now), suffix);
|
||||
|
||||
ut_clean(path);
|
||||
|
||||
return (fopen(utstring_body(path), mode));
|
||||
|
||||
}
|
||||
|
||||
#if 0
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
UT_string *user, *dev;
|
||||
FILE *fp;
|
||||
|
||||
utstring_new(user);
|
||||
utstring_new(dev);
|
||||
|
||||
utstring_printf(user, "%s", argv[1]);
|
||||
utstring_printf(dev, "%s", argv[2]);
|
||||
fp = pathn(user, dev);
|
||||
fputs("hello\n", fp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
5
file.h
Normal file
5
file.h
Normal file
@@ -0,0 +1,5 @@
|
||||
#include "config.h"
|
||||
#include "utstring.h"
|
||||
|
||||
|
||||
FILE *pathn(char *mode, char *prefix, UT_string *user, UT_string *device, char *suffix);
|
||||
179
geo.c
Normal file
179
geo.c
Normal file
@@ -0,0 +1,179 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
#include <curl/curl.h>
|
||||
#include "utstring.h"
|
||||
#include "geo.h"
|
||||
#include "json.h"
|
||||
|
||||
#define GURL "http://maps.googleapis.com/maps/api/geocode/json?latlng=%lf,%lf&sensor=false&language=EN"
|
||||
|
||||
static CURL *curl;
|
||||
|
||||
static size_t writemem(void *contents, size_t size, size_t nmemb, void *userp)
|
||||
{
|
||||
UT_string *cbuf = (UT_string *)userp;
|
||||
size_t realsize = size * nmemb;
|
||||
|
||||
utstring_bincpy(cbuf, contents, realsize);
|
||||
|
||||
return (realsize);
|
||||
}
|
||||
|
||||
static int goog_decode(UT_string *geodata, UT_string *addr, UT_string *cc)
|
||||
{
|
||||
JsonNode *json, *results, *address, *ac, *zeroth;
|
||||
|
||||
/*
|
||||
* We are parsing this. I want the formatted_address in `addr' and
|
||||
* the country code short_name in `cc'
|
||||
*
|
||||
* {
|
||||
* "results" : [
|
||||
* {
|
||||
* "address_components" : [
|
||||
* {
|
||||
* "long_name" : "New Zealand",
|
||||
* "short_name" : "NZ",
|
||||
* "types" : [ "country", "political" ]
|
||||
* }, ...
|
||||
* ],
|
||||
* "formatted_address" : "59 Example Street, Christchurch 8081, New Zealand",
|
||||
*/
|
||||
|
||||
if ((json = json_decode(utstring_body(geodata))) == NULL) {
|
||||
return (0);
|
||||
}
|
||||
|
||||
if ((results = json_find_member(json, "results")) != NULL) {
|
||||
if ((zeroth = json_find_element(results, 0)) != NULL) {
|
||||
address = json_find_member(zeroth, "formatted_address");
|
||||
if ((address != NULL) && (address->tag == JSON_STRING)) {
|
||||
utstring_printf(addr, "%s", address->string_);
|
||||
}
|
||||
}
|
||||
|
||||
/* Country */
|
||||
if ((ac = json_find_member(zeroth, "address_components")) != NULL) {
|
||||
JsonNode *comp, *j;
|
||||
int have_cc = 0;
|
||||
|
||||
json_foreach(comp, ac) {
|
||||
JsonNode *a;
|
||||
|
||||
if ((j = json_find_member(comp, "types")) != NULL) {
|
||||
json_foreach(a, j) {
|
||||
if ((a->tag == JSON_STRING) && (strcmp(a->string_, "country") == 0)) {
|
||||
JsonNode *c;
|
||||
|
||||
if ((c = json_find_member(comp, "short_name")) != NULL) {
|
||||
utstring_printf(cc, "%s", c->string_);
|
||||
have_cc = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (have_cc)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
json_delete(json);
|
||||
return (1);
|
||||
}
|
||||
|
||||
JsonNode *revgeo(double lat, double lon, UT_string *addr, UT_string *cc)
|
||||
{
|
||||
static UT_string *url;
|
||||
static UT_string *cbuf; /* Buffer for curl GET */
|
||||
CURLcode res;
|
||||
int rc;
|
||||
JsonNode *geo;
|
||||
time_t now;
|
||||
|
||||
if ((geo = json_mkobject()) == NULL) {
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
if (lat == 0.0L && lon == 0.0L) {
|
||||
utstring_printf(addr, "Unknown (%lf,%lf)", lat, lon);
|
||||
utstring_printf(cc, "__");
|
||||
return (geo);
|
||||
}
|
||||
|
||||
utstring_renew(url);
|
||||
utstring_renew(cbuf);
|
||||
utstring_printf(url, GURL, lat, lon);
|
||||
|
||||
curl_easy_setopt(curl, CURLOPT_URL, utstring_body(url));
|
||||
curl_easy_setopt(curl, CURLOPT_USERAGENT, "ot-recorder-agent/1.0");
|
||||
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
|
||||
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writemem);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)cbuf);
|
||||
|
||||
res = curl_easy_perform(curl);
|
||||
if (res != CURLE_OK) {
|
||||
utstring_printf(addr, "revgeo failed for (%lf,%lf)", lat, lon);
|
||||
utstring_printf(cc, "__");
|
||||
fprintf(stderr, "curl_easy_perform() failed: %s\n",
|
||||
curl_easy_strerror(res));
|
||||
json_delete(geo);
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
// printf("%s\n", utstring_body(url));
|
||||
|
||||
if (!(rc = goog_decode(cbuf, addr, cc))) {
|
||||
utstring_printf(addr, "Unknown (%lf,%lf)", lat, lon);
|
||||
utstring_printf(cc, "__");
|
||||
}
|
||||
|
||||
fprintf(stderr, "revgeo returns %d: %s\n", rc, utstring_body(addr));
|
||||
|
||||
time(&now);
|
||||
|
||||
json_append_member(geo, "cc", json_mkstring(utstring_body(cc)));
|
||||
json_append_member(geo, "addr", json_mkstring(utstring_body(addr)));
|
||||
json_append_member(geo, "tst", json_mknumber((double)now));
|
||||
return (geo);
|
||||
}
|
||||
|
||||
void revgeo_init()
|
||||
{
|
||||
curl = curl_easy_init();
|
||||
}
|
||||
|
||||
#if 0
|
||||
int main()
|
||||
{
|
||||
double lat = 52.034403, lon = 8.476544;
|
||||
double clat = 51.197500, clon = 6.699179;
|
||||
static UT_string *location;
|
||||
int rc;
|
||||
|
||||
curl = curl_easy_init();
|
||||
|
||||
utstring_renew(location);
|
||||
|
||||
rc = revgeo(lat, lon, location);
|
||||
if (rc == 1) {
|
||||
puts(utstring_body(location));
|
||||
}
|
||||
|
||||
utstring_renew(location);
|
||||
rc = revgeo(clat, clon, location);
|
||||
if (rc == 1) {
|
||||
puts(utstring_body(location));
|
||||
}
|
||||
|
||||
|
||||
|
||||
curl_easy_cleanup(curl);
|
||||
|
||||
return (0);
|
||||
|
||||
}
|
||||
#endif
|
||||
4
geo.h
Normal file
4
geo.h
Normal file
@@ -0,0 +1,4 @@
|
||||
#include "json.h"
|
||||
|
||||
JsonNode *revgeo(double lat, double lon, UT_string *addr, UT_string *cc);
|
||||
void revgeo_init();
|
||||
293
geohash.c
Normal file
293
geohash.c
Normal file
@@ -0,0 +1,293 @@
|
||||
/*
|
||||
* geohash.c
|
||||
* libgeohash
|
||||
*
|
||||
* Created by Derek Smith on 10/6/09.
|
||||
* Copyright (c) 2010, SimpleGeo
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this list
|
||||
* of conditions and the following disclaimer. Redistributions in binary form must
|
||||
* reproduce the above copyright notice, this list of conditions and the following
|
||||
* disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the SimpleGeo nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without specific prior
|
||||
* written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
||||
* THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
|
||||
* GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
|
||||
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
||||
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "geohash.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#define MAX_LAT 90.0
|
||||
#define MIN_LAT -90.0
|
||||
|
||||
#define MAX_LONG 180.0
|
||||
#define MIN_LONG -180.0
|
||||
|
||||
#define NORTH 0
|
||||
#define EAST 1
|
||||
#define SOUTH 2
|
||||
#define WEST 3
|
||||
|
||||
#define LENGTH_OF_DEGREE 111100 // meters
|
||||
|
||||
typedef struct IntervalStruct {
|
||||
|
||||
double high;
|
||||
double low;
|
||||
|
||||
} Interval;
|
||||
|
||||
|
||||
/* Normal 32 characer map used for geohashing */
|
||||
static char char_map[32] = "0123456789bcdefghjkmnpqrstuvwxyz";
|
||||
|
||||
/*
|
||||
* The follow character maps were created by Dave Troy and used in his Javascript Geohashing
|
||||
* library. http://github.com/davetroy/geohash-js
|
||||
*/
|
||||
static char *even_neighbors[] = {"p0r21436x8zb9dcf5h7kjnmqesgutwvy",
|
||||
"bc01fg45238967deuvhjyznpkmstqrwx",
|
||||
"14365h7k9dcfesgujnmqp0r2twvyx8zb",
|
||||
"238967debc01fg45kmstqrwxuvhjyznp"
|
||||
};
|
||||
|
||||
static char *odd_neighbors[] = {"bc01fg45238967deuvhjyznpkmstqrwx",
|
||||
"p0r21436x8zb9dcf5h7kjnmqesgutwvy",
|
||||
"238967debc01fg45kmstqrwxuvhjyznp",
|
||||
"14365h7k9dcfesgujnmqp0r2twvyx8zb"
|
||||
};
|
||||
|
||||
static char *even_borders[] = {"prxz", "bcfguvyz", "028b", "0145hjnp"};
|
||||
static char *odd_borders[] = {"bcfguvyz", "prxz", "0145hjnp", "028b"};
|
||||
|
||||
unsigned int index_for_char(char c, char *string) {
|
||||
|
||||
int index = -1;
|
||||
int string_amount = strlen(string);
|
||||
int i;
|
||||
for(i = 0; i < string_amount; i++) {
|
||||
|
||||
if(c == string[i]) {
|
||||
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
char* get_neighbor(char *hash, int direction) {
|
||||
|
||||
int hash_length = strlen(hash);
|
||||
|
||||
char last_char = hash[hash_length - 1];
|
||||
|
||||
int is_odd = hash_length % 2;
|
||||
char **border = is_odd ? odd_borders : even_borders;
|
||||
char **neighbor = is_odd ? odd_neighbors : even_neighbors;
|
||||
|
||||
char *base = malloc(sizeof(char) * 1);
|
||||
base[0] = '\0';
|
||||
strncat(base, hash, hash_length - 1);
|
||||
|
||||
if(index_for_char(last_char, border[direction]) != -1)
|
||||
base = get_neighbor(base, direction);
|
||||
|
||||
int neighbor_index = index_for_char(last_char, neighbor[direction]);
|
||||
last_char = char_map[neighbor_index];
|
||||
|
||||
char *last_hash = malloc(sizeof(char) * 2);
|
||||
last_hash[0] = last_char;
|
||||
last_hash[1] = '\0';
|
||||
strcat(base, last_hash);
|
||||
free(last_hash);
|
||||
|
||||
return base;
|
||||
}
|
||||
|
||||
char* geohash_encode(double lat, double lng, int precision) {
|
||||
|
||||
if(precision < 1 || precision > 12)
|
||||
precision = 6;
|
||||
|
||||
char* hash = NULL;
|
||||
|
||||
if(lat <= 90.0 && lat >= -90.0 && lng <= 180.0 && lng >= -180.0) {
|
||||
|
||||
hash = (char*)malloc(sizeof(char) * (precision + 1));
|
||||
hash[precision] = '\0';
|
||||
|
||||
precision *= 5.0;
|
||||
|
||||
Interval lat_interval = {MAX_LAT, MIN_LAT};
|
||||
Interval lng_interval = {MAX_LONG, MIN_LONG};
|
||||
|
||||
Interval *interval;
|
||||
double coord, mid;
|
||||
int is_even = 1;
|
||||
unsigned int hashChar = 0;
|
||||
int i;
|
||||
for(i = 1; i <= precision; i++) {
|
||||
|
||||
if(is_even) {
|
||||
|
||||
interval = &lng_interval;
|
||||
coord = lng;
|
||||
|
||||
} else {
|
||||
|
||||
interval = &lat_interval;
|
||||
coord = lat;
|
||||
}
|
||||
|
||||
mid = (interval->low + interval->high) / 2.0;
|
||||
hashChar = hashChar << 1;
|
||||
|
||||
if(coord > mid) {
|
||||
|
||||
interval->low = mid;
|
||||
hashChar |= 0x01;
|
||||
|
||||
} else
|
||||
interval->high = mid;
|
||||
|
||||
if(!(i % 5)) {
|
||||
|
||||
hash[(i - 1) / 5] = char_map[hashChar];
|
||||
hashChar = 0;
|
||||
|
||||
}
|
||||
|
||||
is_even = !is_even;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
GeoCoord geohash_decode(char *hash) {
|
||||
|
||||
GeoCoord coordinate = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
|
||||
|
||||
if(hash) {
|
||||
|
||||
int char_amount = strlen(hash);
|
||||
|
||||
if(char_amount) {
|
||||
|
||||
unsigned int char_mapIndex;
|
||||
Interval lat_interval = {MAX_LAT, MIN_LAT};
|
||||
Interval lng_interval = {MAX_LONG, MIN_LONG};
|
||||
Interval *interval;
|
||||
|
||||
int is_even = 1;
|
||||
double delta;
|
||||
int i, j;
|
||||
for(i = 0; i < char_amount; i++) {
|
||||
|
||||
char_mapIndex = index_for_char(hash[i], (char*)char_map);
|
||||
|
||||
//JPM always false if(char_mapIndex < 0)
|
||||
//JPM break;
|
||||
|
||||
// Interpret the last 5 bits of the integer
|
||||
for(j = 0; j < 5; j++) {
|
||||
|
||||
interval = is_even ? &lng_interval : &lat_interval;
|
||||
|
||||
delta = (interval->high - interval->low) / 2.0;
|
||||
|
||||
if((char_mapIndex << j) & 0x0010)
|
||||
interval->low += delta;
|
||||
else
|
||||
interval->high -= delta;
|
||||
|
||||
is_even = !is_even;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
coordinate.latitude = lat_interval.high - ((lat_interval.high - lat_interval.low) / 2.0);
|
||||
coordinate.longitude = lng_interval.high - ((lng_interval.high - lng_interval.low) / 2.0);
|
||||
|
||||
coordinate.north = lat_interval.high;
|
||||
coordinate.east = lng_interval.high;
|
||||
coordinate.south = lat_interval.low;
|
||||
coordinate.west = lng_interval.low;
|
||||
}
|
||||
}
|
||||
|
||||
return coordinate;
|
||||
}
|
||||
|
||||
|
||||
char** geohash_neighbors(char *hash) {
|
||||
|
||||
char** neighbors = NULL;
|
||||
|
||||
if(hash) {
|
||||
|
||||
// N, NE, E, SE, S, SW, W, NW
|
||||
neighbors = (char**)malloc(sizeof(char*) * 8);
|
||||
|
||||
neighbors[0] = get_neighbor(hash, NORTH);
|
||||
neighbors[1] = get_neighbor(neighbors[0], EAST);
|
||||
neighbors[2] = get_neighbor(hash, EAST);
|
||||
neighbors[3] = get_neighbor(neighbors[2], SOUTH);
|
||||
neighbors[4] = get_neighbor(hash, SOUTH);
|
||||
neighbors[5] = get_neighbor(neighbors[4], WEST);
|
||||
neighbors[6] = get_neighbor(hash, WEST);
|
||||
neighbors[7] = get_neighbor(neighbors[6], NORTH);
|
||||
|
||||
}
|
||||
|
||||
return neighbors;
|
||||
}
|
||||
|
||||
GeoBoxDimension geohash_dimensions_for_precision(int precision) {
|
||||
|
||||
GeoBoxDimension dimensions = {0.0, 0.0};
|
||||
|
||||
if(precision > 0) {
|
||||
|
||||
int lat_times_to_cut = precision * 5 / 2;
|
||||
int lng_times_to_cut = precision * 5 / 2 + (precision % 2 ? 1 : 0);
|
||||
|
||||
double width = 360.0;
|
||||
double height = 180.0;
|
||||
|
||||
int i;
|
||||
for(i = 0; i < lat_times_to_cut; i++)
|
||||
height /= 2.0;
|
||||
|
||||
for(i = 0; i < lng_times_to_cut; i++)
|
||||
width /= 2.0;
|
||||
|
||||
dimensions.width = width;
|
||||
dimensions.height = height;
|
||||
|
||||
}
|
||||
|
||||
return dimensions;
|
||||
}
|
||||
77
geohash.h
Normal file
77
geohash.h
Normal file
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* geohash.h
|
||||
* libgeohash
|
||||
*
|
||||
* Created by Derek Smith on 10/6/09.
|
||||
* Copyright (c) 2010, SimpleGeo
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this list
|
||||
* of conditions and the following disclaimer. Redistributions in binary form must
|
||||
* reproduce the above copyright notice, this list of conditions and the following
|
||||
* disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the SimpleGeo nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without specific prior
|
||||
* written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
||||
* THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
|
||||
* GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
|
||||
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
||||
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
// Metric in meters
|
||||
typedef struct GeoBoxDimensionStruct {
|
||||
|
||||
double height;
|
||||
double width;
|
||||
|
||||
} GeoBoxDimension;
|
||||
|
||||
typedef struct GeoCoordStruct {
|
||||
|
||||
double latitude;
|
||||
double longitude;
|
||||
|
||||
double north;
|
||||
double east;
|
||||
double south;
|
||||
double west;
|
||||
|
||||
GeoBoxDimension dimension;
|
||||
|
||||
} GeoCoord;
|
||||
|
||||
/*
|
||||
* Creates a the hash at the specified precision. If precision is set to 0.
|
||||
* or less than it defaults to 12.
|
||||
*/
|
||||
extern char* geohash_encode(double lat, double lng, int precision);
|
||||
|
||||
/*
|
||||
* Returns the latitude and longitude used to create the hash along with
|
||||
* the bounding box for the encoded coordinate.
|
||||
*/
|
||||
extern GeoCoord geohash_decode(char* hash);
|
||||
|
||||
/*
|
||||
* Return an array of geohashes that represent the neighbors of the passed
|
||||
* in value. The neighbors are indexed as followed:
|
||||
*
|
||||
* N, NE, E, SE, S, SW, W, NW
|
||||
* 0, 1, 2, 3, 4, 5, 6, 7
|
||||
*/
|
||||
extern char** geohash_neighbors(char* hash);
|
||||
|
||||
/*
|
||||
* Returns the width and height of a precision value.
|
||||
*/
|
||||
extern GeoBoxDimension geohash_dimensions_for_precision(int precision);
|
||||
182
ghash.c
Normal file
182
ghash.c
Normal file
@@ -0,0 +1,182 @@
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include "ghash.h"
|
||||
|
||||
void redis_ping(redisContext **redis)
|
||||
{
|
||||
struct timeval timeout = { 1, 500000 }; // 1.5 seconds
|
||||
redisReply *r;
|
||||
int i = 0;
|
||||
|
||||
do {
|
||||
if ((r = redisCommand(*redis,"PING")) != NULL) {
|
||||
// printf("PING: %s\n", r->str);
|
||||
freeReplyObject(r);
|
||||
return;
|
||||
}
|
||||
fprintf(stderr, "REDIS: %d %s\n", (*redis)->err, (*redis)->errstr);
|
||||
|
||||
*redis = redisConnectWithTimeout("localhost", 6379, timeout);
|
||||
|
||||
fprintf(stderr, "Reconnecting to Redis...\n");
|
||||
sleep(5);
|
||||
} while (i++ < 10);
|
||||
|
||||
}
|
||||
|
||||
void ghash_store_redis(redisContext **redis, char *ghash, char *addr, char *cc)
|
||||
{
|
||||
redisReply *r;
|
||||
|
||||
redis_ping(redis);
|
||||
|
||||
r = redisCommand(*redis, "HMSET ghash:%s cc %s addr %s", ghash, cc, addr);
|
||||
if (r) /* FIXME */
|
||||
return;
|
||||
}
|
||||
|
||||
void last_storeredis(redisContext **redis, char *username, char *device, char *jsonstring)
|
||||
{
|
||||
redisReply *r;
|
||||
|
||||
redis_ping(redis);
|
||||
|
||||
r = redisCommand(*redis, "SET lastpos:%s-%s %s", username, device, jsonstring);
|
||||
if (r) /* FIXME */
|
||||
return;
|
||||
}
|
||||
int ghash_get_redis_cache(redisContext **redis, char *ghash, UT_string *addr, UT_string *cc)
|
||||
{
|
||||
redisReply *reply;
|
||||
int found = FALSE;
|
||||
|
||||
redis_ping(redis);
|
||||
|
||||
reply = redisCommand(*redis, "HGETALL ghash:%s", ghash);
|
||||
if (reply == NULL) {
|
||||
fprintf(stderr, "REDIS: %d %s\n", (*redis)->err, (*redis)->errstr);
|
||||
return (FALSE);
|
||||
|
||||
}
|
||||
if ( reply->type == REDIS_REPLY_ERROR ) {
|
||||
fprintf(stderr, "Error: %s\n", reply->str );
|
||||
return (FALSE);
|
||||
}
|
||||
else if ( reply->type != REDIS_REPLY_ARRAY )
|
||||
printf( "Unexpected type: %d\n", reply->type );
|
||||
if (reply->type == REDIS_REPLY_ARRAY) {
|
||||
int i;
|
||||
char *key, *val;
|
||||
|
||||
if (reply->elements >= 1) {
|
||||
for (i = 0; i < (reply->elements - 1); i += 2) {
|
||||
key = reply->element[i]->str;
|
||||
val = reply->element[i+1]->str;
|
||||
|
||||
if (!strcmp(key, "addr"))
|
||||
utstring_printf(addr, "%s", val);
|
||||
else if (!strcmp(key, "cc"))
|
||||
utstring_printf(cc, "%s", val);
|
||||
}
|
||||
found = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
freeReplyObject(reply);
|
||||
|
||||
return (found);
|
||||
}
|
||||
|
||||
int ghash_readcache(struct udata *ud, char *ghash, UT_string *addr, UT_string *cc)
|
||||
{
|
||||
int cached = FALSE;
|
||||
char gfile[BUFSIZ];
|
||||
FILE *fp;
|
||||
|
||||
/* FIXME */
|
||||
|
||||
#ifdef HAVE_REDIS
|
||||
if (ud->useredis) {
|
||||
if (ghash_get_redis_cache( &ud->redis, ghash, addr, cc) == TRUE) {
|
||||
return (TRUE);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (ud->usefiles) {
|
||||
/* if ghash file is available, read cc:addr into that */
|
||||
snprintf(gfile, BUFSIZ, "%s/ghash/%s.json", JSONDIR, ghash);
|
||||
|
||||
fprintf(stderr, "Reading GhashCache from %s\n", gfile);
|
||||
if ((fp = fopen(gfile, "r")) != NULL) {
|
||||
char buf[BUFSIZ];
|
||||
|
||||
/* FIXME: read JSON */
|
||||
if (fgets(buf, sizeof(buf), fp) != NULL) {
|
||||
JsonNode *json, *j;
|
||||
|
||||
if ((json = json_decode(buf)) == NULL) {
|
||||
puts(" FIXME: can't decode JSON");
|
||||
} else {
|
||||
|
||||
if ((j = json_find_member(json, "cc")) != NULL) {
|
||||
if (j->tag == JSON_STRING) {
|
||||
utstring_printf(cc, "%s", j->string_);
|
||||
}
|
||||
}
|
||||
if ((j = json_find_member(json, "addr")) != NULL) {
|
||||
if (j->tag == JSON_STRING) {
|
||||
utstring_printf(addr, "%s", j->string_);
|
||||
}
|
||||
}
|
||||
|
||||
json_delete(json);
|
||||
puts("**** GOTCHA");
|
||||
cached = TRUE;
|
||||
}
|
||||
|
||||
fclose(fp);
|
||||
}
|
||||
} else {
|
||||
fprintf(stderr, "Not cached: ghash: %s\n", gfile);
|
||||
}
|
||||
}
|
||||
|
||||
return (cached);
|
||||
}
|
||||
|
||||
|
||||
void ghash_storecache(struct udata *ud, JsonNode *geo, char *ghash, char *addr, char *cc)
|
||||
{
|
||||
char gfile[BUFSIZ];
|
||||
FILE *fp;
|
||||
|
||||
#ifdef HAVE_REDIS
|
||||
if (ud->useredis) {
|
||||
ghash_store_redis(&ud->redis, ghash, addr, cc);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (ud->usefiles) {
|
||||
snprintf(gfile, BUFSIZ, "%s/ghash", JSONDIR);
|
||||
if (mkpath(gfile) < 0) {
|
||||
perror(gfile);
|
||||
} else {
|
||||
char *js;
|
||||
|
||||
/* I am storing the ghash *in* the JSON purpose */
|
||||
json_append_member(geo, "cc", json_mkstring(ghash));
|
||||
|
||||
if ((js = json_stringify(geo, NULL)) != NULL) {
|
||||
snprintf(gfile, BUFSIZ, "%s/ghash/%s.json", JSONDIR, ghash);
|
||||
|
||||
if ((fp = fopen(gfile, "w")) != NULL) {
|
||||
fprintf(fp, "%s\n", js);
|
||||
fclose(fp);
|
||||
}
|
||||
|
||||
free(js);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
19
ghash.h
Normal file
19
ghash.h
Normal file
@@ -0,0 +1,19 @@
|
||||
#include "config.h"
|
||||
#include "udata.h"
|
||||
#include <hiredis/hiredis.h>
|
||||
#include "geohash.h"
|
||||
#include "utstring.h"
|
||||
#include "json.h"
|
||||
|
||||
#ifndef TRUE
|
||||
# define TRUE 1
|
||||
# define FALSE 0
|
||||
#endif
|
||||
|
||||
void redis_ping(redisContext **redis);
|
||||
// void ghash_store( redisContext **redis, char *ghash, char *addr, char *cc);
|
||||
// int ghash_cached( redisContext **redis, char *ghash, UT_string *addr, UT_string *cc);
|
||||
|
||||
void last_storeredis(redisContext **redis, char *username, char *device, char *jsonstring);
|
||||
int ghash_readcache(struct udata *ud, char *ghash, UT_string *addr, UT_string *cc);
|
||||
void ghash_storecache(struct udata *ud, JsonNode *geo, char *ghash, char *addr, char *cc);
|
||||
3
jget.h
Normal file
3
jget.h
Normal file
@@ -0,0 +1,3 @@
|
||||
#include "json.h"
|
||||
|
||||
JsonNode *jget(JsonNode *json, char *args);
|
||||
117
json.h
Normal file
117
json.h
Normal file
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
Copyright (C) 2011 Joseph A. Adams (joeyadams3.14159@gmail.com)
|
||||
All rights reserved.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef CCAN_JSON_H
|
||||
#define CCAN_JSON_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
typedef enum {
|
||||
JSON_NULL,
|
||||
JSON_BOOL,
|
||||
JSON_STRING,
|
||||
JSON_NUMBER,
|
||||
JSON_ARRAY,
|
||||
JSON_OBJECT,
|
||||
} JsonTag;
|
||||
|
||||
typedef struct JsonNode JsonNode;
|
||||
|
||||
struct JsonNode
|
||||
{
|
||||
/* only if parent is an object or array (NULL otherwise) */
|
||||
JsonNode *parent;
|
||||
JsonNode *prev, *next;
|
||||
|
||||
/* only if parent is an object (NULL otherwise) */
|
||||
char *key; /* Must be valid UTF-8. */
|
||||
|
||||
JsonTag tag;
|
||||
union {
|
||||
/* JSON_BOOL */
|
||||
bool bool_;
|
||||
|
||||
/* JSON_STRING */
|
||||
char *string_; /* Must be valid UTF-8. */
|
||||
|
||||
/* JSON_NUMBER */
|
||||
double number_;
|
||||
|
||||
/* JSON_ARRAY */
|
||||
/* JSON_OBJECT */
|
||||
struct {
|
||||
JsonNode *head, *tail;
|
||||
} children;
|
||||
};
|
||||
};
|
||||
|
||||
/*** Encoding, decoding, and validation ***/
|
||||
|
||||
JsonNode *json_decode (const char *json);
|
||||
char *json_encode (const JsonNode *node);
|
||||
char *json_encode_string (const char *str);
|
||||
char *json_stringify (const JsonNode *node, const char *space);
|
||||
void json_delete (JsonNode *node);
|
||||
|
||||
bool json_validate (const char *json);
|
||||
|
||||
/*** Lookup and traversal ***/
|
||||
|
||||
JsonNode *json_find_element (JsonNode *array, int index);
|
||||
JsonNode *json_find_member (JsonNode *object, const char *key);
|
||||
|
||||
JsonNode *json_first_child (const JsonNode *node);
|
||||
|
||||
#define json_foreach(i, object_or_array) \
|
||||
for ((i) = json_first_child(object_or_array); \
|
||||
(i) != NULL; \
|
||||
(i) = (i)->next)
|
||||
|
||||
/*** Construction and manipulation ***/
|
||||
|
||||
JsonNode *json_mknull(void);
|
||||
JsonNode *json_mkbool(bool b);
|
||||
JsonNode *json_mkstring(const char *s);
|
||||
JsonNode *json_mknumber(double n);
|
||||
JsonNode *json_mkarray(void);
|
||||
JsonNode *json_mkobject(void);
|
||||
|
||||
void json_append_element(JsonNode *array, JsonNode *element);
|
||||
void json_prepend_element(JsonNode *array, JsonNode *element);
|
||||
void json_append_member(JsonNode *object, const char *key, JsonNode *value);
|
||||
void json_prepend_member(JsonNode *object, const char *key, JsonNode *value);
|
||||
|
||||
void json_remove_from_parent(JsonNode *node);
|
||||
|
||||
/*** Debugging ***/
|
||||
|
||||
/*
|
||||
* Look for structure and encoding problems in a JsonNode or its descendents.
|
||||
*
|
||||
* If a problem is detected, return false, writing a description of the problem
|
||||
* to errmsg (unless errmsg is NULL).
|
||||
*/
|
||||
bool json_check(const JsonNode *node, char errmsg[256]);
|
||||
|
||||
#endif
|
||||
74
mkpath.c
Normal file
74
mkpath.c
Normal file
@@ -0,0 +1,74 @@
|
||||
/* $OpenBSD: mkpath.c,v 1.2 2005/06/20 07:14:06 otto Exp $ */
|
||||
/*
|
||||
* Copyright (c) 1983, 1992, 1993
|
||||
* The Regents of the University of California. All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of the University nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <err.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
|
||||
/* Code taken directly from mkdir(1).
|
||||
|
||||
* mkpath -- create directories.
|
||||
* path - path
|
||||
*/
|
||||
int
|
||||
mkpath(char *path)
|
||||
{
|
||||
struct stat sb;
|
||||
char *slash;
|
||||
int done = 0;
|
||||
|
||||
slash = path;
|
||||
|
||||
while (!done) {
|
||||
slash += strspn(slash, "/");
|
||||
slash += strcspn(slash, "/");
|
||||
|
||||
done = (*slash == '\0');
|
||||
*slash = '\0';
|
||||
|
||||
if (stat(path, &sb)) {
|
||||
if (errno != ENOENT || (mkdir(path, 0777) &&
|
||||
errno != EEXIST)) {
|
||||
warn("%s", path);
|
||||
return (-1);
|
||||
}
|
||||
} else if (!S_ISDIR(sb.st_mode)) {
|
||||
warnx("%s: %s", path, strerror(ENOTDIR));
|
||||
return (-1);
|
||||
}
|
||||
|
||||
*slash = '/';
|
||||
}
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
735
ot-recorder.c
Normal file
735
ot-recorder.c
Normal file
@@ -0,0 +1,735 @@
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <signal.h>
|
||||
#include <mosquitto.h>
|
||||
#include <getopt.h>
|
||||
#include <time.h>
|
||||
#include "json.h"
|
||||
#include <sys/utsname.h>
|
||||
#include "utstring.h"
|
||||
#include "utarray.h"
|
||||
#include "geo.h"
|
||||
#include "ghash.h"
|
||||
#include "config.h"
|
||||
#include "file.h"
|
||||
#include "safewrite.h"
|
||||
#include "base64.h"
|
||||
|
||||
|
||||
#ifndef TRUE
|
||||
# define TRUE (1)
|
||||
# define FALSE (0)
|
||||
#endif
|
||||
#define SSL_VERIFY_PEER (1)
|
||||
#define SSL_VERIFY_NONE (0)
|
||||
|
||||
/*
|
||||
* 1 => 5,009.4km x 4,992.6km
|
||||
* 2 => 1,252.3km x 624.1km
|
||||
* 3 => 156.5km x 156km
|
||||
* 4 => 39.1km x 19.5km
|
||||
* 5 => 4.9km x 4.9km
|
||||
* 6 => 1.2km x 609.4m
|
||||
* 7 => 152.9m x 152.4m
|
||||
* 8 => 38.2m x 19m
|
||||
* 9 => 4.8m x 4.8m
|
||||
* 10 => 1.2m x 59.5cm
|
||||
*/
|
||||
|
||||
#define GEOHASH_PREC (7)
|
||||
#define TOPIC_PARTS (4) /* owntracks/user/device/info */
|
||||
#define TOPIC_SUFFIX "info"
|
||||
|
||||
static int run = 1;
|
||||
|
||||
double number(JsonNode *j, char *element)
|
||||
{
|
||||
JsonNode *m;
|
||||
|
||||
if ((m = json_find_member(j, element)) != NULL) {
|
||||
if (m->tag == JSON_NUMBER) {
|
||||
return (m->number_);
|
||||
} else if (m->tag == JSON_STRING) {
|
||||
return (atof(m->string_));
|
||||
}
|
||||
}
|
||||
|
||||
return (-7.0L);
|
||||
}
|
||||
|
||||
JsonNode *extract(struct udata *ud, char *payload, char *tid, char *t, double *lat, double *lon, long *tst)
|
||||
|
||||
{
|
||||
JsonNode *json, *j;
|
||||
|
||||
*tid = *t = 0;
|
||||
*lat = *lon = -1.0L;
|
||||
|
||||
if ((json = json_decode(payload)) == NULL)
|
||||
return (NULL);
|
||||
|
||||
if (ud->skipdemo && (json_find_member(json, "_demo") != NULL)) {
|
||||
json_delete(json);
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
if ((j = json_find_member(json, "_type")) == NULL) {
|
||||
json_delete(json);
|
||||
return (NULL);
|
||||
}
|
||||
if ((j->tag != JSON_STRING) || (strcmp(j->string_, "location") != 0)) {
|
||||
json_delete(json);
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
if ((j = json_find_member(json, "tid")) != NULL) {
|
||||
if (j->tag == JSON_STRING) {
|
||||
// printf("I got: [%s]\n", m->string_);
|
||||
strcpy(tid, j->string_);
|
||||
}
|
||||
}
|
||||
|
||||
if ((j = json_find_member(json, "t")) != NULL) {
|
||||
if (j && j->tag == JSON_STRING) {
|
||||
strcpy(t, j->string_);
|
||||
}
|
||||
}
|
||||
|
||||
*tst = time(NULL);
|
||||
if ((j = json_find_member(json, "tst")) != NULL) {
|
||||
if (j && j->tag == JSON_STRING) {
|
||||
*tst = strtoul(j->string_, NULL, 10);
|
||||
} else {
|
||||
*tst = (unsigned long)j->number_;
|
||||
}
|
||||
}
|
||||
|
||||
*lat = number(json, "lat");
|
||||
*lon = number(json, "lon");
|
||||
|
||||
return (json);
|
||||
}
|
||||
|
||||
static const char *isotime(time_t t) {
|
||||
static char buf[] = "YYYY-MM-DDTHH:MM:SSZ";
|
||||
|
||||
strftime(buf, sizeof(buf), "%FT%TZ", gmtime(&t));
|
||||
return(buf);
|
||||
}
|
||||
|
||||
static const char *ltime(time_t t) {
|
||||
static char buf[] = "HH:MM:SS";
|
||||
|
||||
strftime(buf, sizeof(buf), "%T", localtime(&t));
|
||||
return(buf);
|
||||
}
|
||||
|
||||
void do_info(void *userdata, UT_string *username, UT_string *device, char *payload)
|
||||
{
|
||||
struct udata *ud = (struct udata *)userdata;
|
||||
JsonNode *json, *j;
|
||||
static UT_string *name = NULL, *face = NULL;
|
||||
#ifdef HAVE_REDIS
|
||||
redisReply *r;
|
||||
#endif
|
||||
FILE *fp;
|
||||
char *img;
|
||||
|
||||
utstring_renew(name);
|
||||
utstring_renew(face);
|
||||
|
||||
if ((json = json_decode(payload)) == NULL) {
|
||||
puts("Can't decode INFO payload");
|
||||
return;
|
||||
}
|
||||
|
||||
if (ud->skipdemo && (json_find_member(json, "_demo") != NULL)) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if ((j = json_find_member(json, "_type")) == NULL) {
|
||||
goto cleanup;
|
||||
}
|
||||
if ((j->tag != JSON_STRING) || (strcmp(j->string_, "card") != 0)) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (ud->usefiles) {
|
||||
/* I know the payload is valid JSON: write card */
|
||||
|
||||
if ((fp = pathn("wb", "cards", username, NULL, "json")) != NULL) {
|
||||
fprintf(fp, "%s\n", payload);
|
||||
fclose(fp);
|
||||
}
|
||||
}
|
||||
|
||||
if ((j = json_find_member(json, "name")) != NULL) {
|
||||
if (j->tag == JSON_STRING) {
|
||||
// printf("I got: [%s]\n", j->string_);
|
||||
utstring_printf(name, "%s", j->string_);
|
||||
}
|
||||
}
|
||||
|
||||
if ((j = json_find_member(json, "face")) != NULL) {
|
||||
if (j->tag == JSON_STRING) {
|
||||
// printf("I got: [%s]\n", j->string_);
|
||||
utstring_printf(face, "%s", j->string_);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
fprintf(stderr, "* CARD: %s-%s %s\n", utstring_body(username), utstring_body(device), utstring_body(name));
|
||||
|
||||
|
||||
#ifdef HAVE_REDIS
|
||||
if (ud->useredis) {
|
||||
redis_ping(&ud->redis);
|
||||
r = redisCommand(ud->redis, "HMSET card:%s name %s face %s", utstring_body(username), utstring_body(name), utstring_body(face));
|
||||
if (r) { ; } /* FIXME */
|
||||
}
|
||||
#endif
|
||||
|
||||
/* We have a base64-encoded "face". Decode it and store binary image */
|
||||
if ((img = malloc(utstring_len(face))) != NULL) {
|
||||
int imglen;
|
||||
|
||||
if ((imglen = base64_decode(utstring_body(face), img)) > 0) {
|
||||
if (ud->usefiles) {
|
||||
if ((fp = pathn("wb", "photos", username, NULL, "png")) != NULL) {
|
||||
fwrite(img, sizeof(char), imglen, fp);
|
||||
fclose(fp);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef HAVE_REDIS
|
||||
if (ud->useredis) {
|
||||
/* Add photo (binary) to Redis as photo:username */
|
||||
redis_ping(&ud->redis);
|
||||
r = redisCommand(ud->redis, "SET photo:%s %b", utstring_body(username), img, imglen);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
free(img);
|
||||
}
|
||||
|
||||
|
||||
cleanup:
|
||||
json_delete(json);
|
||||
}
|
||||
|
||||
void do_msg(void *userdata, UT_string *username, UT_string *device, char *payload)
|
||||
{
|
||||
struct udata *ud = (struct udata *)userdata;
|
||||
JsonNode *json, *j;
|
||||
FILE *fp;
|
||||
|
||||
if ((json = json_decode(payload)) == NULL) {
|
||||
puts("Can't decode INFO payload");
|
||||
return;
|
||||
}
|
||||
|
||||
if (ud->skipdemo && (json_find_member(json, "_demo") != NULL)) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if ((j = json_find_member(json, "_type")) == NULL) {
|
||||
goto cleanup;
|
||||
}
|
||||
if ((j->tag != JSON_STRING) || (strcmp(j->string_, "msg") != 0)) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (ud->usefiles) {
|
||||
/* I know the payload is valid JSON: write message */
|
||||
|
||||
if ((fp = pathn("ab", "msg", username, NULL, "json")) != NULL) {
|
||||
fprintf(fp, "%s\n", payload);
|
||||
fclose(fp);
|
||||
}
|
||||
}
|
||||
|
||||
fprintf(stderr, "* MSG: %s-%s\n", utstring_body(username), utstring_body(device));
|
||||
|
||||
cleanup:
|
||||
json_delete(json);
|
||||
}
|
||||
|
||||
void republish(struct mosquitto *mosq, struct udata *userdata, char *username, char *topic, double lat, double lon, char *cc, char *addr, long tst, char *t)
|
||||
{
|
||||
struct udata *ud = (struct udata *)userdata;
|
||||
JsonNode *json;
|
||||
static UT_string *newtopic = NULL;
|
||||
char *payload;
|
||||
|
||||
if (ud->pubprefix == NULL)
|
||||
return;
|
||||
|
||||
if ((json = json_mkobject()) == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
utstring_renew(newtopic);
|
||||
|
||||
utstring_printf(newtopic, "%s/%s", ud->pubprefix, topic);
|
||||
|
||||
json_append_member(json, "username", json_mkstring(username));
|
||||
json_append_member(json, "topic", json_mkstring(topic));
|
||||
json_append_member(json, "cc", json_mkstring(cc));
|
||||
json_append_member(json, "addr", json_mkstring(addr));
|
||||
json_append_member(json, "t", json_mkstring(t));
|
||||
json_append_member(json, "tst", json_mknumber(tst));
|
||||
json_append_member(json, "lat", json_mknumber(lat));
|
||||
json_append_member(json, "lon", json_mknumber(lon));
|
||||
|
||||
|
||||
if ((payload = json_stringify(json, NULL)) != NULL) {
|
||||
mosquitto_publish(mosq, NULL, utstring_body(newtopic),
|
||||
strlen(payload), payload, 1, true);
|
||||
fprintf(stderr, "%s %s\n", utstring_body(newtopic), payload);
|
||||
free(payload);
|
||||
}
|
||||
|
||||
json_delete(json);
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* Decode OwnTracks CSV and return a new JsonNode to a JSON object.
|
||||
*/
|
||||
|
||||
#define MILL 1000000.0
|
||||
|
||||
JsonNode *csv(char *payload, char *tid, char *t, double *lat, double *lon, long *tst)
|
||||
{
|
||||
JsonNode *json = NULL;
|
||||
double dist = 0;
|
||||
char tmptst[40];
|
||||
double vel, trip, alt, cog;
|
||||
|
||||
if (sscanf(payload, "%[^,],%[^,],%[^,],%lf,%lf,%lf,%lf,%lf,%lf,%lf", tid, tmptst, t, lat, lon, &cog, &vel, &alt, &dist, &trip) != 10) {
|
||||
fprintf(stderr, "**** payload not CSV: %s\n", payload);
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
*lat /= MILL;
|
||||
*lon /= MILL;
|
||||
cog *= 10;
|
||||
alt *= 10;
|
||||
trip *= 1000;
|
||||
|
||||
*tst = strtoul(tmptst, NULL, 16);
|
||||
|
||||
sprintf(tmptst, "%ld", *tst);
|
||||
|
||||
json = json_mkobject();
|
||||
json_append_member(json, "_type", json_mkstring("location"));
|
||||
json_append_member(json, "t", json_mkstring(t));
|
||||
json_append_member(json, "tid", json_mkstring(tid));
|
||||
json_append_member(json, "tst", json_mkstring(tmptst));
|
||||
json_append_member(json, "lat", json_mknumber(*lat));
|
||||
json_append_member(json, "lon", json_mknumber(*lon));
|
||||
json_append_member(json, "cog", json_mknumber(cog));
|
||||
json_append_member(json, "vel", json_mknumber(vel));
|
||||
json_append_member(json, "alt", json_mknumber(alt));
|
||||
json_append_member(json, "dist", json_mknumber(dist));
|
||||
json_append_member(json, "trip", json_mknumber(trip));
|
||||
json_append_member(json, "csv", json_mkbool(1));
|
||||
|
||||
return (json);
|
||||
}
|
||||
#define RECFORMAT "%s\t%-18s\t%s\n"
|
||||
|
||||
void on_message(struct mosquitto *mosq, void *userdata, const struct mosquitto_message *m)
|
||||
{
|
||||
JsonNode *json;
|
||||
char tid[BUFSIZ], t[BUFSIZ], *p;
|
||||
double lat, lon;
|
||||
long tst;
|
||||
struct udata *ud = (struct udata *)userdata;
|
||||
FILE *fp;
|
||||
char **topics;
|
||||
int count = 0, cached;
|
||||
static UT_string *basetopic = NULL, *username = NULL, *device = NULL, *addr = NULL, *cc = NULL, *ghash = NULL, *ts = NULL;
|
||||
static UT_string *rest = NULL;
|
||||
char *jsonstring;
|
||||
time_t now;
|
||||
|
||||
/*
|
||||
* mosquitto_message->
|
||||
* int mid;
|
||||
* char *topic;
|
||||
* void *payload;
|
||||
* int payloadlen;
|
||||
* int qos;
|
||||
* bool retain;
|
||||
*/
|
||||
|
||||
if (m->payloadlen == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
printf("%s %s\n", m->topic, m->payload); fflush(stdout);
|
||||
|
||||
|
||||
utstring_renew(ts);
|
||||
utstring_renew(basetopic);
|
||||
utstring_renew(username);
|
||||
utstring_renew(device);
|
||||
|
||||
if (mosquitto_sub_topic_tokenise(m->topic, &topics, &count) != MOSQ_ERR_SUCCESS) {
|
||||
return;
|
||||
}
|
||||
|
||||
time(&now);
|
||||
|
||||
/* FIXME: handle null leading topic `/` */
|
||||
utstring_printf(basetopic, "%s/%s/%s", topics[0], topics[1], topics[2]);
|
||||
utstring_printf(username, "%s", topics[1]);
|
||||
utstring_printf(device, "%s", topics[2]);
|
||||
|
||||
if ((count == TOPIC_PARTS) && (strcmp(topics[count-1], TOPIC_SUFFIX) == 0)) {
|
||||
do_info(ud, username, device, m->payload);
|
||||
}
|
||||
|
||||
/* owntracks/user/device/msg */
|
||||
if ((count == TOPIC_PARTS) && (strcmp(topics[count-1], "msg") == 0)) {
|
||||
if (m->retain == FALSE || ud->ignoreretained == FALSE) {
|
||||
do_msg(ud, username, device, m->payload);
|
||||
}
|
||||
}
|
||||
|
||||
if (count != 3) {
|
||||
/*
|
||||
* Not a normal location publish. Build up a string consisting of the remaining
|
||||
* topic parts, i.e. whatever is after base topic, and record with whatever
|
||||
* (hopefully non-binary) payload we got.
|
||||
*/
|
||||
|
||||
int j;
|
||||
|
||||
utstring_renew(rest);
|
||||
for (j = 3; j < count; j++) {
|
||||
utstring_printf(rest, "%s%c", topics[j], (j < count - 1) ? '/' : ' ');
|
||||
}
|
||||
|
||||
|
||||
if (ud->usefiles) {
|
||||
if ((fp = pathn("a", "rec", username, device, "rec")) != NULL) {
|
||||
|
||||
/* TODO: check payload for binary characters and hex-dump? */
|
||||
fprintf(fp, RECFORMAT, isotime(now), utstring_body(rest), m->payload);
|
||||
fclose(fp);
|
||||
}
|
||||
}
|
||||
|
||||
mosquitto_sub_topic_tokens_free(&topics, count);
|
||||
return;
|
||||
}
|
||||
mosquitto_sub_topic_tokens_free(&topics, count);
|
||||
|
||||
if (m->retain == TRUE && ud->ignoreretained) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Decode JSON payload and ensure _type: location */
|
||||
json = extract(ud, m->payload, tid, t, &lat, &lon, &tst);
|
||||
if (json == NULL) {
|
||||
/* Is it OwnTracks Greenwich CSV? */
|
||||
|
||||
if ((json = csv(m->payload, tid, t, &lat, &lon, &tst)) == NULL) {
|
||||
return;
|
||||
}
|
||||
fprintf(stderr, "+++++ %s\n", json_stringify(json, NULL));
|
||||
}
|
||||
|
||||
#if 0
|
||||
if (*t && (!strcmp(t, "p") || !strcmp(t, "b"))) {
|
||||
// fprintf(stderr, "Ignore `t:%s' for %s\n", t, m->topic);
|
||||
json_delete(json);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
utstring_renew(ghash);
|
||||
p = geohash_encode(lat, lon, GEOHASH_PREC);
|
||||
if (p != NULL) {
|
||||
utstring_printf(ghash, "%s", p);
|
||||
free(p);
|
||||
}
|
||||
|
||||
utstring_renew(addr);
|
||||
utstring_renew(cc);
|
||||
|
||||
cached = FALSE;
|
||||
|
||||
/* FIXME: */
|
||||
|
||||
cached = ghash_readcache(ud, utstring_body(ghash), addr, cc);
|
||||
if (!cached) {
|
||||
JsonNode *geo;
|
||||
|
||||
if ((geo = revgeo(lat, lon, addr, cc)) != NULL) {
|
||||
fprintf(stderr, "REVGEO: %s\n", utstring_body(addr));
|
||||
ghash_storecache(ud, geo, utstring_body(ghash), utstring_body(addr), utstring_body(cc));
|
||||
json_delete(geo);
|
||||
}
|
||||
}
|
||||
|
||||
/* FIXME: check if this is a location type */
|
||||
/*
|
||||
* We have exactly three topic parts (owntracks/user/device), and valid JSON.
|
||||
* Add a few bits to the JSON, and record it on a per-user/device basis.
|
||||
*/
|
||||
|
||||
json_append_member(json, "ghash", json_mkstring(utstring_body(ghash)));
|
||||
|
||||
if ((jsonstring = json_stringify(json, NULL)) != NULL) {
|
||||
|
||||
#ifdef HAVE_REDIS
|
||||
if (ud->useredis) {
|
||||
/* add last to Redis as "lastpos:username-device" */
|
||||
last_storeredis(&ud->redis, utstring_body(username), utstring_body(device), jsonstring);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (ud->usefiles) {
|
||||
if ((fp = pathn("a", "rec", username, device, "rec")) != NULL) {
|
||||
|
||||
// fprintf(fp, "%s\n", jsonstring);
|
||||
fprintf(fp, RECFORMAT, isotime(now), "*", jsonstring);
|
||||
|
||||
/* Now safewrite the last location */
|
||||
utstring_printf(ts, "%s/last", JSONDIR);
|
||||
if (mkpath(utstring_body(ts)) < 0) {
|
||||
perror(utstring_body(ts));
|
||||
}
|
||||
utstring_printf(ts, "/%s-%s.json",
|
||||
utstring_body(username), utstring_body(device));
|
||||
|
||||
safewrite(utstring_body(ts), jsonstring);
|
||||
|
||||
|
||||
|
||||
fclose(fp);
|
||||
}
|
||||
}
|
||||
free(jsonstring);
|
||||
}
|
||||
|
||||
/* publish */
|
||||
republish(mosq, ud, utstring_body(username), m->topic, lat, lon, utstring_body(cc), utstring_body(addr), tst, t);
|
||||
|
||||
fprintf(stderr, "%c %s %-7s %s [%s] %s\n",
|
||||
(cached) ? '*' : '-',
|
||||
utstring_body(ghash), m->topic, ltime(tst), utstring_body(cc), utstring_body(addr));
|
||||
|
||||
|
||||
json_delete(json);
|
||||
}
|
||||
|
||||
void on_connect(struct mosquitto *mosq, void *userdata, int rc)
|
||||
{
|
||||
struct udata *ud = (struct udata *)userdata;
|
||||
int mid;
|
||||
char **m = NULL;
|
||||
|
||||
while ((m = (char **)utarray_next(ud->topics, m))) {
|
||||
fprintf(stderr, "Subscribing to %s\n", *m);
|
||||
mosquitto_subscribe(mosq, &mid, *m, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void on_disconnect(struct mosquitto *mosq, void *userdata, int reason)
|
||||
{
|
||||
#ifdef HAVE_REDIS
|
||||
struct udata *ud = (struct udata *)userdata;
|
||||
#endif
|
||||
|
||||
fprintf(stderr, "Disconnected. Reason: %d [%s]\n", reason, mosquitto_strerror(reason));
|
||||
|
||||
if (reason == 0) { // client wish
|
||||
#ifdef HAVE_REDIS
|
||||
redisFree(ud->redis);
|
||||
#endif
|
||||
//SQL sqlite3_finalize(ud->raw);
|
||||
//SQL sqlite3_finalize(ud->lastloc);
|
||||
//SQL sqlite3_close(ud->db);
|
||||
run = FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
static void catcher(int sig)
|
||||
{
|
||||
fprintf(stderr, "Going down on signal %d\n", sig);
|
||||
|
||||
exit(1);
|
||||
}
|
||||
|
||||
void usage(char *prog)
|
||||
{
|
||||
fprintf(stderr, "Usage: %s [-D] [-F] [-N] [-P prefix] [-R] topic [topic...]\n", prog);
|
||||
exit(2);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
struct mosquitto *mosq = NULL;
|
||||
char err[1024], *p, *username, *password, *cafile;
|
||||
char *hostname = "localhost";
|
||||
int port = 1883;
|
||||
int rc, i, ch;
|
||||
static struct udata udata, *ud = &udata;
|
||||
struct utsname uts;
|
||||
UT_string *clientid;
|
||||
#ifdef HAVE_REDIS
|
||||
struct timeval timeout = { 1, 500000 }; // 1.5 seconds
|
||||
#endif
|
||||
char *progname = *argv;
|
||||
|
||||
udata.usefiles = TRUE;
|
||||
udata.ignoreretained = TRUE;
|
||||
udata.pubprefix = NULL;
|
||||
udata.skipdemo = TRUE;
|
||||
udata.useredis = TRUE;
|
||||
|
||||
while ((ch = getopt(argc, argv, "DFRNP:")) != EOF) {
|
||||
switch (ch) {
|
||||
case 'D':
|
||||
ud->skipdemo = FALSE;
|
||||
break;
|
||||
case 'F':
|
||||
ud->usefiles = FALSE;
|
||||
break;
|
||||
case 'N':
|
||||
ud->useredis = FALSE;
|
||||
break;
|
||||
case 'P':
|
||||
udata.pubprefix = strdup(optarg);
|
||||
break;
|
||||
case 'R':
|
||||
ud->ignoreretained = FALSE;
|
||||
break;
|
||||
default:
|
||||
usage(*argv);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
argc -= (optind - 1);
|
||||
argv += (optind - 1);
|
||||
|
||||
if (argc < 2) {
|
||||
usage(progname);
|
||||
return (-1);
|
||||
}
|
||||
|
||||
if ((p = getenv("OTR_HOST")) != NULL) {
|
||||
hostname = strdup(p);
|
||||
}
|
||||
|
||||
if ((p = getenv("OTR_PORT")) != NULL) {
|
||||
port = atoi(p);
|
||||
}
|
||||
|
||||
revgeo_init();
|
||||
|
||||
utstring_new(clientid);
|
||||
|
||||
mosquitto_lib_init();
|
||||
|
||||
utstring_printf(clientid, "ot-recorder");
|
||||
if (uname(&uts) == 0) {
|
||||
utstring_printf(clientid, "-%s", uts.nodename);
|
||||
}
|
||||
utstring_printf(clientid, "-%d", getpid());
|
||||
|
||||
signal(SIGINT, catcher);
|
||||
|
||||
#ifdef HAVE_REDIS
|
||||
ud->redis = redisConnectWithTimeout("localhost", 6379, timeout);
|
||||
#endif
|
||||
|
||||
mosq = mosquitto_new(utstring_body(clientid), true, (void *)&udata);
|
||||
if (!mosq) {
|
||||
fprintf(stderr, "Error: Out of memory.\n");
|
||||
mosquitto_lib_cleanup();
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Pushing list of topics into the array so that we can (re)subscribe on_connect()
|
||||
*/
|
||||
|
||||
utarray_new(ud->topics, &ut_str_icd);
|
||||
for (i = 1; i < argc; i++) {
|
||||
utarray_push_back(ud->topics, &argv[i]);
|
||||
}
|
||||
|
||||
mosquitto_reconnect_delay_set(mosq,
|
||||
2, /* delay */
|
||||
20, /* delay_max */
|
||||
0); /* exponential backoff */
|
||||
|
||||
mosquitto_message_callback_set(mosq, on_message);
|
||||
mosquitto_connect_callback_set(mosq, on_connect);
|
||||
mosquitto_disconnect_callback_set(mosq, on_disconnect);
|
||||
|
||||
if ((username = getenv("OTR_USER")) != NULL) {
|
||||
if ((password = getenv("OTR_PASS")) != NULL) {
|
||||
mosquitto_username_pw_set(mosq, username, password);
|
||||
}
|
||||
}
|
||||
|
||||
cafile = getenv("OTR_CAFILE");
|
||||
|
||||
if (cafile && *cafile) {
|
||||
|
||||
rc = mosquitto_tls_set(mosq,
|
||||
cafile, /* cafile */
|
||||
NULL, /* capath */
|
||||
NULL, /* certfile */
|
||||
NULL, /* keyfile */
|
||||
NULL /* pw_callback() */
|
||||
);
|
||||
if (rc != MOSQ_ERR_SUCCESS) {
|
||||
fprintf(stderr, "Cannot set TLS CA: %s (check path names)\n",
|
||||
mosquitto_strerror(rc));
|
||||
exit(3);
|
||||
}
|
||||
|
||||
mosquitto_tls_opts_set(mosq,
|
||||
SSL_VERIFY_PEER,
|
||||
NULL, /* tls_version: "tlsv1.2", "tlsv1" */
|
||||
NULL /* ciphers */
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
|
||||
rc = mosquitto_connect(mosq, hostname, port, 60);
|
||||
if (rc) {
|
||||
if (rc == MOSQ_ERR_ERRNO) {
|
||||
strerror_r(errno, err, 1024);
|
||||
fprintf(stderr, "Error: %s\n", err);
|
||||
} else {
|
||||
fprintf(stderr, "Unable to connect (%d) [%s].\n", rc, mosquitto_strerror(rc));
|
||||
}
|
||||
mosquitto_lib_cleanup();
|
||||
return rc;
|
||||
}
|
||||
|
||||
while (run) {
|
||||
mosquitto_loop_forever(mosq, -1, 1);
|
||||
}
|
||||
|
||||
mosquitto_disconnect(mosq);
|
||||
|
||||
mosquitto_destroy(mosq);
|
||||
mosquitto_lib_cleanup();
|
||||
|
||||
return (0);
|
||||
}
|
||||
56
safewrite.c
Normal file
56
safewrite.c
Normal file
@@ -0,0 +1,56 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
#include "safewrite.h"
|
||||
|
||||
int safewrite(char *filename, char *buf)
|
||||
{
|
||||
char *tmpfile = malloc(strlen(filename) + 3);
|
||||
mode_t mode = S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH;
|
||||
int fd;
|
||||
|
||||
if (tmpfile == NULL)
|
||||
return (-1);
|
||||
|
||||
sprintf(tmpfile, "%s~", filename);
|
||||
|
||||
if (unlink(tmpfile) == -1) {
|
||||
if (errno != ENOENT) {
|
||||
fprintf(stderr, "Failed to remove %s (errno=%d)\n", tmpfile, errno);
|
||||
free(tmpfile);
|
||||
return (-1);
|
||||
}
|
||||
}
|
||||
|
||||
if ((fd = open(tmpfile, O_RDWR|O_CREAT|O_TRUNC, mode)) == -1) {
|
||||
fprintf(stderr, "Failed to create %s (errno=%d)\n", tmpfile, errno);
|
||||
free(tmpfile);
|
||||
return (-1);
|
||||
}
|
||||
|
||||
if (write(fd, buf, strlen(buf)) != strlen(buf)) {
|
||||
fprintf(stderr, "Failed to write to %s (errno=%d)\n", tmpfile, errno);
|
||||
free(tmpfile);
|
||||
close(fd);
|
||||
return (-1);
|
||||
}
|
||||
/* Ensure NL-terminated */
|
||||
if (buf[strlen(buf) - 1] != '\n') {
|
||||
write(fd, "\n", 1);
|
||||
}
|
||||
|
||||
close(fd);
|
||||
|
||||
if ((rename(tmpfile, filename)) == -1) {
|
||||
fprintf(stderr, "Failed to rename %s to %s (errno=%d)\n", tmpfile, filename, errno);
|
||||
free(tmpfile);
|
||||
}
|
||||
|
||||
free(tmpfile);
|
||||
return (0);
|
||||
}
|
||||
3
safewrite.h
Normal file
3
safewrite.h
Normal file
@@ -0,0 +1,3 @@
|
||||
|
||||
int safewrite(char *filename, char *buf);
|
||||
|
||||
18
udata.h
Normal file
18
udata.h
Normal file
@@ -0,0 +1,18 @@
|
||||
#include "config.h"
|
||||
#include "utarray.h"
|
||||
#ifdef HAVE_REDIS
|
||||
# include <hiredis/hiredis.h>
|
||||
#endif
|
||||
|
||||
|
||||
struct udata {
|
||||
UT_array *topics; /* Array of topics to subscribe to */
|
||||
#ifdef HAVE_REDIS
|
||||
redisContext *redis;
|
||||
#endif
|
||||
int usefiles; /* True if files to be written */
|
||||
int ignoreretained; /* True if retained messages should be ignored */
|
||||
char *pubprefix; /* If not NULL (default), republish modified payload to <pubprefix>/topic */
|
||||
int skipdemo; /* True if _demo users are to be skipped */
|
||||
int useredis; /* True if we should do Redis (if we have it) */
|
||||
};
|
||||
232
utarray.h
Normal file
232
utarray.h
Normal file
@@ -0,0 +1,232 @@
|
||||
/*
|
||||
Copyright (c) 2008-2014, Troy D. Hanson http://troydhanson.github.com/uthash/
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
|
||||
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
||||
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
||||
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
|
||||
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
/* a dynamic array implementation using macros
|
||||
*/
|
||||
#ifndef UTARRAY_H
|
||||
#define UTARRAY_H
|
||||
|
||||
#define UTARRAY_VERSION 1.9.9
|
||||
|
||||
#ifdef __GNUC__
|
||||
#define _UNUSED_ __attribute__ ((__unused__))
|
||||
#else
|
||||
#define _UNUSED_
|
||||
#endif
|
||||
|
||||
#include <stddef.h> /* size_t */
|
||||
#include <string.h> /* memset, etc */
|
||||
#include <stdlib.h> /* exit */
|
||||
|
||||
#define oom() exit(-1)
|
||||
|
||||
typedef void (ctor_f)(void *dst, const void *src);
|
||||
typedef void (dtor_f)(void *elt);
|
||||
typedef void (init_f)(void *elt);
|
||||
typedef struct {
|
||||
size_t sz;
|
||||
init_f *init;
|
||||
ctor_f *copy;
|
||||
dtor_f *dtor;
|
||||
} UT_icd;
|
||||
|
||||
typedef struct {
|
||||
unsigned i,n;/* i: index of next available slot, n: num slots */
|
||||
UT_icd icd; /* initializer, copy and destructor functions */
|
||||
char *d; /* n slots of size icd->sz*/
|
||||
} UT_array;
|
||||
|
||||
#define utarray_init(a,_icd) do { \
|
||||
memset(a,0,sizeof(UT_array)); \
|
||||
(a)->icd=*_icd; \
|
||||
} while(0)
|
||||
|
||||
#define utarray_done(a) do { \
|
||||
if ((a)->n) { \
|
||||
if ((a)->icd.dtor) { \
|
||||
size_t _ut_i; \
|
||||
for(_ut_i=0; _ut_i < (a)->i; _ut_i++) { \
|
||||
(a)->icd.dtor(utarray_eltptr(a,_ut_i)); \
|
||||
} \
|
||||
} \
|
||||
free((a)->d); \
|
||||
} \
|
||||
(a)->n=0; \
|
||||
} while(0)
|
||||
|
||||
#define utarray_new(a,_icd) do { \
|
||||
a=(UT_array*)malloc(sizeof(UT_array)); \
|
||||
utarray_init(a,_icd); \
|
||||
} while(0)
|
||||
|
||||
#define utarray_free(a) do { \
|
||||
utarray_done(a); \
|
||||
free(a); \
|
||||
} while(0)
|
||||
|
||||
#define utarray_reserve(a,by) do { \
|
||||
if (((a)->i+(by)) > ((a)->n)) { \
|
||||
while(((a)->i+(by)) > ((a)->n)) { (a)->n = ((a)->n ? (2*(a)->n) : 8); } \
|
||||
if ( ((a)->d=(char*)realloc((a)->d, (a)->n*(a)->icd.sz)) == NULL) oom(); \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
#define utarray_push_back(a,p) do { \
|
||||
utarray_reserve(a,1); \
|
||||
if ((a)->icd.copy) { (a)->icd.copy( _utarray_eltptr(a,(a)->i++), p); } \
|
||||
else { memcpy(_utarray_eltptr(a,(a)->i++), p, (a)->icd.sz); }; \
|
||||
} while(0)
|
||||
|
||||
#define utarray_pop_back(a) do { \
|
||||
if ((a)->icd.dtor) { (a)->icd.dtor( _utarray_eltptr(a,--((a)->i))); } \
|
||||
else { (a)->i--; } \
|
||||
} while(0)
|
||||
|
||||
#define utarray_extend_back(a) do { \
|
||||
utarray_reserve(a,1); \
|
||||
if ((a)->icd.init) { (a)->icd.init(_utarray_eltptr(a,(a)->i)); } \
|
||||
else { memset(_utarray_eltptr(a,(a)->i),0,(a)->icd.sz); } \
|
||||
(a)->i++; \
|
||||
} while(0)
|
||||
|
||||
#define utarray_len(a) ((a)->i)
|
||||
|
||||
#define utarray_eltptr(a,j) (((j) < (a)->i) ? _utarray_eltptr(a,j) : NULL)
|
||||
#define _utarray_eltptr(a,j) ((char*)((a)->d + ((a)->icd.sz*(j) )))
|
||||
|
||||
#define utarray_insert(a,p,j) do { \
|
||||
if (j > (a)->i) utarray_resize(a,j); \
|
||||
utarray_reserve(a,1); \
|
||||
if ((j) < (a)->i) { \
|
||||
memmove( _utarray_eltptr(a,(j)+1), _utarray_eltptr(a,j), \
|
||||
((a)->i - (j))*((a)->icd.sz)); \
|
||||
} \
|
||||
if ((a)->icd.copy) { (a)->icd.copy( _utarray_eltptr(a,j), p); } \
|
||||
else { memcpy(_utarray_eltptr(a,j), p, (a)->icd.sz); }; \
|
||||
(a)->i++; \
|
||||
} while(0)
|
||||
|
||||
#define utarray_inserta(a,w,j) do { \
|
||||
if (utarray_len(w) == 0) break; \
|
||||
if (j > (a)->i) utarray_resize(a,j); \
|
||||
utarray_reserve(a,utarray_len(w)); \
|
||||
if ((j) < (a)->i) { \
|
||||
memmove(_utarray_eltptr(a,(j)+utarray_len(w)), \
|
||||
_utarray_eltptr(a,j), \
|
||||
((a)->i - (j))*((a)->icd.sz)); \
|
||||
} \
|
||||
if ((a)->icd.copy) { \
|
||||
size_t _ut_i; \
|
||||
for(_ut_i=0;_ut_i<(w)->i;_ut_i++) { \
|
||||
(a)->icd.copy(_utarray_eltptr(a,j+_ut_i), _utarray_eltptr(w,_ut_i)); \
|
||||
} \
|
||||
} else { \
|
||||
memcpy(_utarray_eltptr(a,j), _utarray_eltptr(w,0), \
|
||||
utarray_len(w)*((a)->icd.sz)); \
|
||||
} \
|
||||
(a)->i += utarray_len(w); \
|
||||
} while(0)
|
||||
|
||||
#define utarray_resize(dst,num) do { \
|
||||
size_t _ut_i; \
|
||||
if (dst->i > (size_t)(num)) { \
|
||||
if ((dst)->icd.dtor) { \
|
||||
for(_ut_i=num; _ut_i < dst->i; _ut_i++) { \
|
||||
(dst)->icd.dtor(utarray_eltptr(dst,_ut_i)); \
|
||||
} \
|
||||
} \
|
||||
} else if (dst->i < (size_t)(num)) { \
|
||||
utarray_reserve(dst,num-dst->i); \
|
||||
if ((dst)->icd.init) { \
|
||||
for(_ut_i=dst->i; _ut_i < num; _ut_i++) { \
|
||||
(dst)->icd.init(utarray_eltptr(dst,_ut_i)); \
|
||||
} \
|
||||
} else { \
|
||||
memset(_utarray_eltptr(dst,dst->i),0,(dst)->icd.sz*(num-dst->i)); \
|
||||
} \
|
||||
} \
|
||||
dst->i = num; \
|
||||
} while(0)
|
||||
|
||||
#define utarray_concat(dst,src) do { \
|
||||
utarray_inserta((dst),(src),utarray_len(dst)); \
|
||||
} while(0)
|
||||
|
||||
#define utarray_erase(a,pos,len) do { \
|
||||
if ((a)->icd.dtor) { \
|
||||
size_t _ut_i; \
|
||||
for(_ut_i=0; _ut_i < len; _ut_i++) { \
|
||||
(a)->icd.dtor(utarray_eltptr((a),pos+_ut_i)); \
|
||||
} \
|
||||
} \
|
||||
if ((a)->i > (pos+len)) { \
|
||||
memmove( _utarray_eltptr((a),pos), _utarray_eltptr((a),pos+len), \
|
||||
(((a)->i)-(pos+len))*((a)->icd.sz)); \
|
||||
} \
|
||||
(a)->i -= (len); \
|
||||
} while(0)
|
||||
|
||||
#define utarray_renew(a,u) do { \
|
||||
if (a) utarray_clear(a); \
|
||||
else utarray_new((a),(u)); \
|
||||
} while(0)
|
||||
|
||||
#define utarray_clear(a) do { \
|
||||
if ((a)->i > 0) { \
|
||||
if ((a)->icd.dtor) { \
|
||||
size_t _ut_i; \
|
||||
for(_ut_i=0; _ut_i < (a)->i; _ut_i++) { \
|
||||
(a)->icd.dtor(utarray_eltptr(a,_ut_i)); \
|
||||
} \
|
||||
} \
|
||||
(a)->i = 0; \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
#define utarray_sort(a,cmp) do { \
|
||||
qsort((a)->d, (a)->i, (a)->icd.sz, cmp); \
|
||||
} while(0)
|
||||
|
||||
#define utarray_find(a,v,cmp) bsearch((v),(a)->d,(a)->i,(a)->icd.sz,cmp)
|
||||
|
||||
#define utarray_front(a) (((a)->i) ? (_utarray_eltptr(a,0)) : NULL)
|
||||
#define utarray_next(a,e) (((e)==NULL) ? utarray_front(a) : ((((a)->i) > (utarray_eltidx(a,e)+1)) ? _utarray_eltptr(a,utarray_eltidx(a,e)+1) : NULL))
|
||||
#define utarray_prev(a,e) (((e)==NULL) ? utarray_back(a) : ((utarray_eltidx(a,e) > 0) ? _utarray_eltptr(a,utarray_eltidx(a,e)-1) : NULL))
|
||||
#define utarray_back(a) (((a)->i) ? (_utarray_eltptr(a,(a)->i-1)) : NULL)
|
||||
#define utarray_eltidx(a,e) (((char*)(e) >= (char*)((a)->d)) ? (((char*)(e) - (char*)((a)->d))/(size_t)(a)->icd.sz) : -1)
|
||||
|
||||
/* last we pre-define a few icd for common utarrays of ints and strings */
|
||||
static void utarray_str_cpy(void *dst, const void *src) {
|
||||
char **_src = (char**)src, **_dst = (char**)dst;
|
||||
*_dst = (*_src == NULL) ? NULL : strdup(*_src);
|
||||
}
|
||||
static void utarray_str_dtor(void *elt) {
|
||||
char **eltc = (char**)elt;
|
||||
if (*eltc) free(*eltc);
|
||||
}
|
||||
static const UT_icd ut_str_icd _UNUSED_ = {sizeof(char*),NULL,utarray_str_cpy,utarray_str_dtor};
|
||||
static const UT_icd ut_int_icd _UNUSED_ = {sizeof(int),NULL,NULL,NULL};
|
||||
static const UT_icd ut_ptr_icd _UNUSED_ = {sizeof(void*),NULL,NULL,NULL};
|
||||
|
||||
|
||||
#endif /* UTARRAY_H */
|
||||
393
utstring.h
Normal file
393
utstring.h
Normal file
@@ -0,0 +1,393 @@
|
||||
/*
|
||||
Copyright (c) 2008-2014, Troy D. Hanson http://troydhanson.github.com/uthash/
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
|
||||
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
||||
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
||||
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
|
||||
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
/* a dynamic string implementation using macros
|
||||
*/
|
||||
#ifndef UTSTRING_H
|
||||
#define UTSTRING_H
|
||||
|
||||
#define UTSTRING_VERSION 1.9.9
|
||||
|
||||
#ifdef __GNUC__
|
||||
#define _UNUSED_ __attribute__ ((__unused__))
|
||||
#else
|
||||
#define _UNUSED_
|
||||
#endif
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <stdarg.h>
|
||||
#define oom() exit(-1)
|
||||
|
||||
typedef struct {
|
||||
char *d;
|
||||
size_t n; /* allocd size */
|
||||
size_t i; /* index of first unused byte */
|
||||
} UT_string;
|
||||
|
||||
#define utstring_reserve(s,amt) \
|
||||
do { \
|
||||
if (((s)->n - (s)->i) < (size_t)(amt)) { \
|
||||
(s)->d = (char*)realloc((s)->d, (s)->n + (amt)); \
|
||||
if ((s)->d == NULL) oom(); \
|
||||
(s)->n += (amt); \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
#define utstring_init(s) \
|
||||
do { \
|
||||
(s)->n = 0; (s)->i = 0; (s)->d = NULL; \
|
||||
utstring_reserve(s,100); \
|
||||
(s)->d[0] = '\0'; \
|
||||
} while(0)
|
||||
|
||||
#define utstring_done(s) \
|
||||
do { \
|
||||
if ((s)->d != NULL) free((s)->d); \
|
||||
(s)->n = 0; \
|
||||
} while(0)
|
||||
|
||||
#define utstring_free(s) \
|
||||
do { \
|
||||
utstring_done(s); \
|
||||
free(s); \
|
||||
} while(0)
|
||||
|
||||
#define utstring_new(s) \
|
||||
do { \
|
||||
s = (UT_string*)calloc(sizeof(UT_string),1); \
|
||||
if (!s) oom(); \
|
||||
utstring_init(s); \
|
||||
} while(0)
|
||||
|
||||
#define utstring_renew(s) \
|
||||
do { \
|
||||
if (s) { \
|
||||
utstring_clear(s); \
|
||||
} else { \
|
||||
utstring_new(s); \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
#define utstring_clear(s) \
|
||||
do { \
|
||||
(s)->i = 0; \
|
||||
(s)->d[0] = '\0'; \
|
||||
} while(0)
|
||||
|
||||
#define utstring_bincpy(s,b,l) \
|
||||
do { \
|
||||
utstring_reserve((s),(l)+1); \
|
||||
if (l) memcpy(&(s)->d[(s)->i], b, l); \
|
||||
(s)->i += (l); \
|
||||
(s)->d[(s)->i]='\0'; \
|
||||
} while(0)
|
||||
|
||||
#define utstring_concat(dst,src) \
|
||||
do { \
|
||||
utstring_reserve((dst),((src)->i)+1); \
|
||||
if ((src)->i) memcpy(&(dst)->d[(dst)->i], (src)->d, (src)->i); \
|
||||
(dst)->i += (src)->i; \
|
||||
(dst)->d[(dst)->i]='\0'; \
|
||||
} while(0)
|
||||
|
||||
#define utstring_len(s) ((unsigned)((s)->i))
|
||||
|
||||
#define utstring_body(s) ((s)->d)
|
||||
|
||||
_UNUSED_ static void utstring_printf_va(UT_string *s, const char *fmt, va_list ap) {
|
||||
int n;
|
||||
va_list cp;
|
||||
while (1) {
|
||||
#ifdef _WIN32
|
||||
cp = ap;
|
||||
#else
|
||||
va_copy(cp, ap);
|
||||
#endif
|
||||
n = vsnprintf (&s->d[s->i], s->n-s->i, fmt, cp);
|
||||
va_end(cp);
|
||||
|
||||
if ((n > -1) && ((size_t) n < (s->n-s->i))) {
|
||||
s->i += n;
|
||||
return;
|
||||
}
|
||||
|
||||
/* Else try again with more space. */
|
||||
if (n > -1) utstring_reserve(s,n+1); /* exact */
|
||||
else utstring_reserve(s,(s->n)*2); /* 2x */
|
||||
}
|
||||
}
|
||||
#ifdef __GNUC__
|
||||
/* support printf format checking (2=the format string, 3=start of varargs) */
|
||||
static void utstring_printf(UT_string *s, const char *fmt, ...)
|
||||
__attribute__ (( format( printf, 2, 3) ));
|
||||
#endif
|
||||
_UNUSED_ static void utstring_printf(UT_string *s, const char *fmt, ...) {
|
||||
va_list ap;
|
||||
va_start(ap,fmt);
|
||||
utstring_printf_va(s,fmt,ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* begin substring search functions *
|
||||
******************************************************************************/
|
||||
/* Build KMP table from left to right. */
|
||||
_UNUSED_ static void _utstring_BuildTable(
|
||||
const char *P_Needle,
|
||||
size_t P_NeedleLen,
|
||||
long *P_KMP_Table)
|
||||
{
|
||||
long i, j;
|
||||
|
||||
i = 0;
|
||||
j = i - 1;
|
||||
P_KMP_Table[i] = j;
|
||||
while (i < (long) P_NeedleLen)
|
||||
{
|
||||
while ( (j > -1) && (P_Needle[i] != P_Needle[j]) )
|
||||
{
|
||||
j = P_KMP_Table[j];
|
||||
}
|
||||
i++;
|
||||
j++;
|
||||
if (i < (long) P_NeedleLen)
|
||||
{
|
||||
if (P_Needle[i] == P_Needle[j])
|
||||
{
|
||||
P_KMP_Table[i] = P_KMP_Table[j];
|
||||
}
|
||||
else
|
||||
{
|
||||
P_KMP_Table[i] = j;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
P_KMP_Table[i] = j;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
/* Build KMP table from right to left. */
|
||||
_UNUSED_ static void _utstring_BuildTableR(
|
||||
const char *P_Needle,
|
||||
size_t P_NeedleLen,
|
||||
long *P_KMP_Table)
|
||||
{
|
||||
long i, j;
|
||||
|
||||
i = P_NeedleLen - 1;
|
||||
j = i + 1;
|
||||
P_KMP_Table[i + 1] = j;
|
||||
while (i >= 0)
|
||||
{
|
||||
while ( (j < (long) P_NeedleLen) && (P_Needle[i] != P_Needle[j]) )
|
||||
{
|
||||
j = P_KMP_Table[j + 1];
|
||||
}
|
||||
i--;
|
||||
j--;
|
||||
if (i >= 0)
|
||||
{
|
||||
if (P_Needle[i] == P_Needle[j])
|
||||
{
|
||||
P_KMP_Table[i + 1] = P_KMP_Table[j + 1];
|
||||
}
|
||||
else
|
||||
{
|
||||
P_KMP_Table[i + 1] = j;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
P_KMP_Table[i + 1] = j;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
/* Search data from left to right. ( Multiple search mode. ) */
|
||||
_UNUSED_ static long _utstring_find(
|
||||
const char *P_Haystack,
|
||||
size_t P_HaystackLen,
|
||||
const char *P_Needle,
|
||||
size_t P_NeedleLen,
|
||||
long *P_KMP_Table)
|
||||
{
|
||||
long i, j;
|
||||
long V_FindPosition = -1;
|
||||
|
||||
/* Search from left to right. */
|
||||
i = j = 0;
|
||||
while ( (j < (int)P_HaystackLen) && (((P_HaystackLen - j) + i) >= P_NeedleLen) )
|
||||
{
|
||||
while ( (i > -1) && (P_Needle[i] != P_Haystack[j]) )
|
||||
{
|
||||
i = P_KMP_Table[i];
|
||||
}
|
||||
i++;
|
||||
j++;
|
||||
if (i >= (int)P_NeedleLen)
|
||||
{
|
||||
/* Found. */
|
||||
V_FindPosition = j - i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return V_FindPosition;
|
||||
}
|
||||
|
||||
|
||||
/* Search data from right to left. ( Multiple search mode. ) */
|
||||
_UNUSED_ static long _utstring_findR(
|
||||
const char *P_Haystack,
|
||||
size_t P_HaystackLen,
|
||||
const char *P_Needle,
|
||||
size_t P_NeedleLen,
|
||||
long *P_KMP_Table)
|
||||
{
|
||||
long i, j;
|
||||
long V_FindPosition = -1;
|
||||
|
||||
/* Search from right to left. */
|
||||
j = (P_HaystackLen - 1);
|
||||
i = (P_NeedleLen - 1);
|
||||
while ( (j >= 0) && (j >= i) )
|
||||
{
|
||||
while ( (i < (int)P_NeedleLen) && (P_Needle[i] != P_Haystack[j]) )
|
||||
{
|
||||
i = P_KMP_Table[i + 1];
|
||||
}
|
||||
i--;
|
||||
j--;
|
||||
if (i < 0)
|
||||
{
|
||||
/* Found. */
|
||||
V_FindPosition = j + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return V_FindPosition;
|
||||
}
|
||||
|
||||
|
||||
/* Search data from left to right. ( One time search mode. ) */
|
||||
_UNUSED_ static long utstring_find(
|
||||
UT_string *s,
|
||||
long P_StartPosition, /* Start from 0. -1 means last position. */
|
||||
const char *P_Needle,
|
||||
size_t P_NeedleLen)
|
||||
{
|
||||
long V_StartPosition;
|
||||
long V_HaystackLen;
|
||||
long *V_KMP_Table;
|
||||
long V_FindPosition = -1;
|
||||
|
||||
if (P_StartPosition < 0)
|
||||
{
|
||||
V_StartPosition = s->i + P_StartPosition;
|
||||
}
|
||||
else
|
||||
{
|
||||
V_StartPosition = P_StartPosition;
|
||||
}
|
||||
V_HaystackLen = s->i - V_StartPosition;
|
||||
if ( (V_HaystackLen >= (long) P_NeedleLen) && (P_NeedleLen > 0) )
|
||||
{
|
||||
V_KMP_Table = (long *)malloc(sizeof(long) * (P_NeedleLen + 1));
|
||||
if (V_KMP_Table != NULL)
|
||||
{
|
||||
_utstring_BuildTable(P_Needle, P_NeedleLen, V_KMP_Table);
|
||||
|
||||
V_FindPosition = _utstring_find(s->d + V_StartPosition,
|
||||
V_HaystackLen,
|
||||
P_Needle,
|
||||
P_NeedleLen,
|
||||
V_KMP_Table);
|
||||
if (V_FindPosition >= 0)
|
||||
{
|
||||
V_FindPosition += V_StartPosition;
|
||||
}
|
||||
|
||||
free(V_KMP_Table);
|
||||
}
|
||||
}
|
||||
|
||||
return V_FindPosition;
|
||||
}
|
||||
|
||||
|
||||
/* Search data from right to left. ( One time search mode. ) */
|
||||
_UNUSED_ static long utstring_findR(
|
||||
UT_string *s,
|
||||
long P_StartPosition, /* Start from 0. -1 means last position. */
|
||||
const char *P_Needle,
|
||||
size_t P_NeedleLen)
|
||||
{
|
||||
long V_StartPosition;
|
||||
long V_HaystackLen;
|
||||
long *V_KMP_Table;
|
||||
long V_FindPosition = -1;
|
||||
|
||||
if (P_StartPosition < 0)
|
||||
{
|
||||
V_StartPosition = s->i + P_StartPosition;
|
||||
}
|
||||
else
|
||||
{
|
||||
V_StartPosition = P_StartPosition;
|
||||
}
|
||||
V_HaystackLen = V_StartPosition + 1;
|
||||
if ( (V_HaystackLen >= (long) P_NeedleLen) && (P_NeedleLen > 0) )
|
||||
{
|
||||
V_KMP_Table = (long *)malloc(sizeof(long) * (P_NeedleLen + 1));
|
||||
if (V_KMP_Table != NULL)
|
||||
{
|
||||
_utstring_BuildTableR(P_Needle, P_NeedleLen, V_KMP_Table);
|
||||
|
||||
V_FindPosition = _utstring_findR(s->d,
|
||||
V_HaystackLen,
|
||||
P_Needle,
|
||||
P_NeedleLen,
|
||||
V_KMP_Table);
|
||||
|
||||
free(V_KMP_Table);
|
||||
}
|
||||
}
|
||||
|
||||
return V_FindPosition;
|
||||
}
|
||||
/*******************************************************************************
|
||||
* end substring search functions *
|
||||
******************************************************************************/
|
||||
|
||||
#endif /* UTSTRING_H */
|
||||
Reference in New Issue
Block a user