diff --git a/src/calcelestial.c b/src/calcelestial.c index 4bf84bf..2ebc553 100644 --- a/src/calcelestial.c +++ b/src/calcelestial.c @@ -44,7 +44,6 @@ #include "../config.h" #include "objects.h" -#include "helpers.h" #include "formatter.h" #include "geonames.h" @@ -59,6 +58,7 @@ static struct option long_options[] = { {"lon", required_argument, 0, 'o'}, #ifdef GEONAMES_SUPPORT {"query", required_argument, 0, 'q'}, + {"local", no_argument, 0, 'l'}, #endif {"timezone", required_argument, 0, 'z'}, {"universal", no_argument, 0, 'u'}, @@ -70,16 +70,17 @@ static struct option long_options[] = { static const char *long_options_descs[] = { "calc for celestial object: sun, moon, mars, neptune,\n\t\t\t jupiter, mercury, uranus, saturn, venus or pluto", "calc rise/set time with twilight: nautic, civil or astronomical", - "calc at given time: YYYY-MM-DD [HH:MM:SS]", + "calc at given time: YYYY-MM-DD[_HH:MM:SS]", "calc position at moment of: rise, set, transit", "use rise, set, transit time of tomorrow", "output format: see strftime (3) and calcelestial (1) for more details", "geographical latitude of observer: -90° to 90°", "geographical longitude of oberserver: -180° to 180°", #ifdef GEONAMES_SUPPORT - "query geonames.org for geographical coordinates", + "query coordinates using the geonames.org geolocation service", + "query local timezone using the geonames.org geolocation service", #endif - "override system timezone", + "override system timezone (TZ environment variable)", "use universial time for parsing and formatting", "show usage help", "show version" @@ -120,7 +121,7 @@ int main(int argc, char *argv[]) int ret; time_t t; double jd; - struct tm *date = NULL; + struct tm tm; const struct object *obj; /* Default options */ @@ -128,12 +129,16 @@ int main(int argc, char *argv[]) int tz = INT_MAX; char *obj_str = basename(argv[0]); - char *format = "time: %Y-%m-%d %H:%M:%S az: §a (§s) alt: §h"; + char *format = "time: %Y-%m-%d %H:%M:%S (%Z) az: §a (§s) alt: §h"; + char tzid[32] = ""; char *query = NULL; bool horizon_set = false; - bool utc = false; bool next = false; + bool local_tz = false; + + time(&t); + localtime_r(&t, &tm); enum { MOMENT_NOW, @@ -147,7 +152,7 @@ int main(int argc, char *argv[]) /* parse command line arguments */ while (1) { - char c = getopt_long(argc, argv, "+hvnut:d:f:a:o:q:z:p:m:H:", long_options, NULL); + char c = getopt_long(argc, argv, "+hvnult:d:f:a:o:q:z:p:m:H:", long_options, NULL); /* detect the end of the options. */ if (c == -1) @@ -173,13 +178,14 @@ int main(int argc, char *argv[]) break; case 't': - date = malloc(sizeof(struct tm)); - date->tm_isdst = -1; /* update dst */ - if (strptime(optarg, "%Y-%m-%d %H:%M:%S", date)) { } - else if (strptime(optarg, "%Y-%m-%d", date)) { } + tm.tm_isdst = -1; /* update dst */ + if (strchr(optarg, '_')) { + if (!strptime(optarg, "%Y-%m-%d_%H:%M:%S", &tm)) + usage_error("invalid time/date parameter"); + } else { - free(date); - usage_error("invalid date parameter"); + if (!strptime(optarg, "%Y-%m-%d", &tm)) + usage_error("invalid time/date parameter"); } break; @@ -213,19 +219,21 @@ int main(int argc, char *argv[]) case 'q': query = strdup(optarg); break; -#endif + case 'l': + local_tz = true; + break; +#endif case 'p': obj_str = optarg; break; case 'z': - tz = atoi(optarg); + strncpy(tzid, optarg, sizeof(tzid)); break; case 'u': - utc = true; - tz = 0; + strncpy(tzid, "UTC", sizeof(tzid)); break; case 'v': @@ -250,33 +258,35 @@ int main(int argc, char *argv[]) #ifdef GEONAMES_SUPPORT /* Lookup place at http://geonames.org */ if (query) { - ret = geonames_lookup(query, &obs, NULL, 0); + ret = geonames_lookup_latlng(query, &obs, NULL, 0); + if (ret) + usage_error("failed to lookup location"); + } + + if (local_tz) { + int gmt_offset; + ret = geonames_lookup_tz(obs, &gmt_offset, tzid, sizeof(tzid)); if (ret) usage_error("failed to lookup location"); } #endif + setenv("TZ", tzid, 1); + tzset(); + /* Validate observer coordinates */ if (fabs(obs.lat) > 90) usage_error("invalid latitude, use --lat"); if (fabs(obs.lng) > 180) usage_error("invalid longitude, use --lon"); - if (horizon_set && !strcmp(object_name(obj), "sun")) + if (horizon_set && strcmp(object_name(obj), "sun")) usage_error("the twilight parameter can only be used for the sun"); /* Calculate julian date */ - if (date) { - t = (utc) ? mktimeutc(date) : mktime(date); - free(date); - } - else - t = time(NULL); - + t = mktime(&tm); jd = ln_get_julian_from_timet(&t); - date = localtime(&t); - result.tz = (tz == INT_MAX) ? date->tm_gmtoff / 3600 : tz; result.obs = obs; #ifdef DEBUG @@ -285,7 +295,7 @@ int main(int argc, char *argv[]) printf("Debug: for position: N %f, E %f\n", obs.lat, obs.lng); printf("Debug: for object: %s\n", object_name(obj)); printf("Debug: with horizon: %f\n", horizon); - printf("Debug: with timezone: %d\n", result.tz); + printf("Debug: with timezone: %s\n", tzid); #endif /* calc rst date */ @@ -309,11 +319,12 @@ rst: if (object_rst(obj, jd - .5, horizon, &result.obs, &result.rst) == 1) { goto rst; } } + + ln_get_timet_from_julian(result.jd, &t); + localtime_r(&t, &result.tm); - /* calc position */ - object_pos(obj, result.jd, &result); + object_pos(obj, jd, &result); - /* format & output */ format_result(format, &result); return 0; diff --git a/src/geonames.c b/src/geonames.c index 1a6240a..6e67094 100644 --- a/src/geonames.c +++ b/src/geonames.c @@ -39,176 +39,38 @@ #include "../config.h" #include "geonames.h" +#include "formatter.h" -const char* username = "libastro"; -const char* request_url_tpl = "http://api.geonames.org/search?name=%s&maxRows=1&username=%s&type=json&orderby=relevance"; +static const char* url_tpl = "http://api.geonames.org/search?q=%s&maxRows=1&username=libastro&type=json&orderby=relevance"; +static const char* url_tz_tpl = "http://api.geonames.org/timezoneJSON?lat=%.6f&lng=%.6f&username=libastro"; -static size_t json_parse_callback(void *contents, size_t size, size_t nmemb, void *userp) -{ - static struct json_tokener *jtok; - static struct json_object *jobj; - size_t realsize = size * nmemb; +struct string { + char *ptr; + size_t len; +}; - /* initialize tokener */ - if (jtok == NULL) { - jtok = json_tokener_new(); - jtok->err = json_tokener_continue; - } +struct ctx_latlng { + struct ln_lnlat_posn *coords; + char *name; + size_t namelen; +}; - if (jtok->err == json_tokener_continue) { -#ifdef DEBUG - printf("Debug: received chunk: %zu * %zu = %zu bytes\r\n", size, nmemb, realsize); - printf(" %.*s\r\n", (int) realsize, (char *) contents); -#endif +struct ctx_tz { + int *gmt_offset; + char *tzid; + size_t tzidlen; +}; - jobj = json_tokener_parse_ex(jtok, (char *) contents, realsize); - - if (jtok->err == json_tokener_success) { - *(struct json_object **) userp = jobj; - json_tokener_free(jtok); - } - else if (jtok->err != json_tokener_continue) { - const char *err = json_tokener_error_desc(json_tokener_get_error(jtok)); - fprintf(stderr, "json parse error: %s\r\n", err); - *(void **) userp = NULL; - json_tokener_free(jtok); - } - } - - return realsize; -} - -int geonames_lookup(const char *place, struct ln_lnlat_posn *result, char *name, int n) -{ #ifdef GEONAMES_CACHE_SUPPORT - int ret; - - ret = geonames_cache_db(1, place, result); - if (ret == 0) { - strncpy(name, place, n); -#ifdef DEBUG - printf("Debug: using cached geonames entry\n"); -#endif - return 0; - } -#endif +enum cache_op { STORE, LOOKUP, DELETE }; - CURL *ch; - CURLcode res; - - struct json_object *jobj; - - /* setup curl */ - ch = curl_easy_init(); - if (!ch) - return -1; - - /* prepare url */ - int len = strlen(place) + strlen(request_url_tpl) + 1; - char *request_url = malloc(len); - if (!request_url) - return -2; - - snprintf(request_url, len, request_url_tpl, place, username); - -#ifdef DEBUG - printf("Debug: request url: %s\r\n", request_url); -#endif - - curl_easy_setopt(ch, CURLOPT_URL, request_url); - curl_easy_setopt(ch, CURLOPT_WRITEFUNCTION, json_parse_callback); - curl_easy_setopt(ch, CURLOPT_WRITEDATA, (void *) &jobj); - curl_easy_setopt(ch, CURLOPT_USERAGENT, "libastro/1.0"); - - /* perform request */ - res = curl_easy_perform(ch); - - /* always cleanup */ - curl_easy_cleanup(ch); - - if (res != CURLE_OK) { - fprintf(stderr, "request failed: %s\n", curl_easy_strerror(res)); - return -1; - } - - if (jobj) { - int ret = geonames_parse(jobj, result, name, n); - if (!ret) { -#ifdef GEONAMES_CACHE_SUPPORT - geonames_cache_db(0, place, result); -#ifdef DEBUG - printf("Debug: storing cache entry\n"); -#endif -#endif - } - - json_object_put(jobj); - - return ret; - } - else - return -1; -} - -int geonames_parse(struct json_object *jobj, struct ln_lnlat_posn *result, char *name, int n) -{ - json_bool exists; - json_object *jobj_count, *jobj_geonames, *jobj_place, *jobj_lat, *jobj_lng, *jobj_name; - int results; - - exists = json_object_object_get_ex(jobj, "totalResultsCount", &jobj_count); - if (!exists) - return -1; - - exists = json_object_object_get_ex(jobj, "geonames", &jobj_geonames); - if (!exists) - return -1; - - results = json_object_get_int(jobj_count); - if (results == 0) - return -1; - - jobj_place = json_object_array_get_idx(jobj_geonames, 0); - if (!jobj_place) - return -1; - - exists = json_object_object_get_ex(jobj_place, "lat", &jobj_lat); - if (!exists) - return -1; - - exists = json_object_object_get_ex(jobj_place, "lng", &jobj_lng); - if (!exists) - return -1; - - exists = json_object_object_get_ex(jobj_name, "name", &jobj_name); - if (!exists) - return -1; - - result->lat = json_object_get_double(jobj_lat); - result->lng = json_object_get_double(jobj_lng); - - if (name && n > 0) - strncpy(name, json_object_get_string(jobj_name), n); - - return 0; -} - -int geonames_cache_db(int lookup, const char *place, struct ln_lnlat_posn *coords) +static int cache(enum cache_op op, const char *url, struct string *s) { int ret; DB *dbp; DBT key, data; char filename[256]; - char *place_lower; - - place_lower = strdup(place); - if (!place_lower) - return -1; - - for (char *p = place_lower; *p; ++p) - *p = tolower(*p); - snprintf(filename, sizeof(filename), "%s/%s", getenv("HOME"), GEONAMES_CACHE_FILE); dbp = dbopen(filename, O_RDWR | O_CREAT, 0664, DB_BTREE, NULL); @@ -220,37 +82,43 @@ int geonames_cache_db(int lookup, const char *place, struct ln_lnlat_posn *coord memset(&key, 0, sizeof(key)); memset(&data, 0, sizeof(data)); - key.data = place_lower; - key.size = strlen(place_lower) + 1; + key.data = (void *) url; + key.size = strlen(url) + 1; - if (lookup) { - ret = dbp->get(dbp, &key, &data, 0); - if (ret) - goto err; + switch (op) { + case LOOKUP: + ret = dbp->get(dbp, &key, &data, 0); + if (ret) + goto err; #ifdef DEBUG - printf("Debug: cache key retrieved: %s => %f %f.\n", (char *) key.data, - ((struct ln_lnlat_posn *) data.data)->lat, - ((struct ln_lnlat_posn *) data.data)->lng); -#endif - if (data.size != sizeof(struct ln_lnlat_posn)) - goto err; + printf("Debug: cache key retrieved: %s => %s.\n", (char *) key.data, (char *) data.data); +#endif /* DEBUG */ + s->ptr = malloc(data.size); + s->len = data.size; + if (!s->ptr) { + fprintf(stderr, "malloc() failed: %s\n", strerror(errno)); + exit(1); + } - memcpy(coords, data.data, sizeof(struct ln_lnlat_posn)); - } - else { - data.data = coords; - data.size = sizeof(struct ln_lnlat_posn); + memcpy(s->ptr, data.data, data.size); + break; - ret = dbp->put(dbp, &key, &data, 0); - if (ret) - goto err; - + case STORE: + data.data = s->ptr; + data.size = s->len; + + ret = dbp->put(dbp, &key, &data, 0); + if (ret) + goto err; #ifdef DEBUG - printf("Debug: cache key stored: %s => %f %f.\n", (char *) key.data, - ((struct ln_lnlat_posn *) data.data)->lat, - ((struct ln_lnlat_posn *) data.data)->lng); -#endif + printf("Debug: cache key stored: %s => %s\n", (char *) key.data, (char *) data.data); +#endif /* DEBUG */ + break; + + case DELETE: { + // TODO + } } err: @@ -258,3 +126,201 @@ err: return ret; } +#endif /* GEONAMES_CACHE_SUPPORT */ + +static size_t writefunction(void *contents, size_t size, size_t nmemb, void *userp) +{ + struct string *s = userp; + + size_t new_len = s->len + size*nmemb; + + s->ptr = realloc(s->ptr, new_len + 1); + if (s->ptr == NULL) { + fprintf(stderr, "realloc() failed\n"); + exit(EXIT_FAILURE); + } + + memcpy(s->ptr + s->len, contents, size*nmemb); + + s->ptr[new_len] = '\0'; + s->len = new_len; + + return size * nmemb; +} + +static int request_json(const char *url, int (*parser)(struct json_object *jobj, void *ctx), void *ctx) +{ + int ret, cached; + + CURL *ch; + CURLcode res; + + struct json_object *jobj; + enum json_tokener_error error; + + struct string s = { 0 }; + +#ifdef GEONAMES_CACHE_SUPPORT + cached = cache(LOOKUP, url, &s) == 0; + if (cached) + goto cached; +#endif /* GEONAMES_CACHE_SUPPORT */ + + /* Setup curl */ + ch = curl_easy_init(); + if (!ch) + return -1; + +#ifdef DEBUG + printf("Debug: request url: %s\r\n", url); +#endif /* DEBUG */ + + curl_easy_setopt(ch, CURLOPT_URL, url); + curl_easy_setopt(ch, CURLOPT_WRITEFUNCTION, writefunction); + curl_easy_setopt(ch, CURLOPT_WRITEDATA, (void *) &s); + curl_easy_setopt(ch, CURLOPT_USERAGENT, "libastro/1.0"); + + /* perform request */ + res = curl_easy_perform(ch); + + /* always cleanup */ + curl_easy_cleanup(ch); + + if (res != CURLE_OK) { + fprintf(stderr, "Error: request failed: %s\n", curl_easy_strerror(res)); + return -1; + } + +#ifdef DEBUG + printf("Debug: request completed: %s\r\n", s.ptr); +#endif /* DEBUG */ + +cached: + jobj = json_tokener_parse_verbose(s.ptr, &error); + if (!jobj) { +#ifdef DEBUG + printf("Debug: failed to parse json: %s\r\n", json_tokener_error_desc(error)); +#endif /* DEBUG */ + } + + + ret = parser(jobj, ctx); + if (!ret && !cached) { +#ifdef GEONAMES_CACHE_SUPPORT + cache(STORE, url, &s); +#endif /* GEONAMES_CACHE_SUPPORT */ + } +#ifdef DEBUG + else + printf("Debug: failed to parse: %d\n", ret); +#endif /* DEBUG */ + + json_object_put(jobj); + + return ret; +} + +static int parser_tz(struct json_object *jobj, void *userp) +{ + struct ctx_tz *ctx = userp; + + json_bool exists; + struct json_object *jobj_offset, *jobj_tzid; + + exists = json_object_object_get_ex(jobj, "gmtOffset", &jobj_offset); + if (!exists) + return -1; + + exists = json_object_object_get_ex(jobj, "timezoneId", &jobj_tzid); + if (!exists) + return -1; + + *ctx->gmt_offset = json_object_get_int(jobj_offset); + + if (ctx->tzid) + strncpy(ctx->tzid, json_object_get_string(jobj_tzid), ctx->tzidlen); + + return 0; +} + +static int parser_latlng(struct json_object *jobj, void *userp) +{ + struct ctx_latlng *ctx = userp; + + json_bool exists; + struct json_object *jobj_count, *jobj_geonames, *jobj_place, *jobj_lat, *jobj_lng, *jobj_name; + int results; + + exists = json_object_object_get_ex(jobj, "totalResultsCount", &jobj_count); + if (!exists) + return -1; + + exists = json_object_object_get_ex(jobj, "geonames", &jobj_geonames); + if (!exists) + return -2; + + results = json_object_get_int(jobj_count); + if (results == 0) + return -3; + + jobj_place = json_object_array_get_idx(jobj_geonames, 0); + if (!jobj_place) + return -4; + + exists = json_object_object_get_ex(jobj_place, "lat", &jobj_lat); + if (!exists) + return -5; + + exists = json_object_object_get_ex(jobj_place, "lng", &jobj_lng); + if (!exists) + return -6; + + exists = json_object_object_get_ex(jobj_place, "name", &jobj_name); + if (!exists) + return -7; + + ctx->coords->lat = json_object_get_double(jobj_lat); + ctx->coords->lng = json_object_get_double(jobj_lng); + + if (ctx->name) + strncpy(ctx->name, json_object_get_string(jobj_name), ctx->namelen); + + return 0; +} + +int geonames_lookup_tz(struct ln_lnlat_posn coords, int *gmt_offset, char *tzid, size_t tzidlen) +{ + char url[256]; + struct ctx_tz ctx = { + .gmt_offset = gmt_offset, + .tzid = tzid, + .tzidlen = tzidlen + }; + + snprintf(url, sizeof(url), url_tz_tpl, coords.lat, coords.lng); + + return request_json(url, parser_tz, &ctx); +} + +int geonames_lookup_latlng(const char *place, struct ln_lnlat_posn *coords, char *name, size_t namelen) +{ + int ret; + char url[256]; + struct ctx_latlng ctx = { + .coords = coords, + .name = name, + .namelen = namelen + }; + + //char *escaped_place = curl_escape(place, 0); + char *escaped_place = strrepl(place, " ", "+"); + + snprintf(url, sizeof(url), url_tpl, escaped_place); + + ret = request_json(url, parser_latlng, &ctx); + + //curl_free(escaped_place); + free(escaped_place); + + return ret; +} \ No newline at end of file diff --git a/src/geonames.h b/src/geonames.h index 6e72118..2690fda 100644 --- a/src/geonames.h +++ b/src/geonames.h @@ -32,10 +32,9 @@ struct ln_lnlat_posn; #define GEONAMES_CACHE_SUPPORT 1 -#define GEONAMES_CACHE_FILE ".geonames.cache" /* in users home dir */ +#define GEONAMES_CACHE_FILE ".geonames.db" /* in users home dir */ -int geonames_lookup(const char *place, struct ln_lnlat_posn *coords, char *name, int n); -int geonames_cache_db(int lookup, const char *place, struct ln_lnlat_posn *coords); -int geonames_parse(struct json_object *jobj, struct ln_lnlat_posn *result, char *name, int n); +int geonames_lookup_latlng(const char *place, struct ln_lnlat_posn *coords, char *name, size_t namelen); +int geonames_lookup_tz(struct ln_lnlat_posn coords, int *gmt_offset, char *tzid, size_t tzidlen); #endif /* _GEONAMES_H_ */ diff --git a/src/geonames_main.c b/src/geonames_main.c index 5d2dc20..38f04c2 100644 --- a/src/geonames_main.c +++ b/src/geonames_main.c @@ -32,21 +32,24 @@ #include "geonames.h" int main(int argc, char *argv[]) { + int ret, gmt_offset; struct ln_lnlat_posn res; - char *result_name, *name; - - result_name = malloc(128); - if (result_name == NULL) - return -1; + char name[128], tzid[32]; if (argc != 2) fprintf(stderr, "Usage: geonames LOCATION\n"); - int ret = geonames_lookup(argv[1], &res, result_name, 32); - if (!ret) - printf("%s is at (%.4f, %.4f)\r\n", result_name, res.lat, res.lng); - - free(result_name); + ret = geonames_lookup_latlng(argv[1], &res, name, sizeof(name)); + if (ret) { + fprintf(stderr, "Error: Failed to lookup coordinates of %s\n", argv[1]); + return 1; + } + + ret = geonames_lookup_tz(res, &gmt_offset, tzid, sizeof(tzid)); + if (ret) + fprintf(stderr, "Error: Failed to lookup timezone for %.4f %.4f\n", res.lat, res.lng); + + printf("%s is at %.4f, %.4f with timezone %s (GMT%+d)\r\n", name, res.lat, res.lng, tzid, gmt_offset); return ret; }