Matt Stancliff avatar Matt Stancliff committed 5b92617

Rebar-ize libgeoip-erlang

To build:
rebar complie

To clean:
rebar clean

To bundle:
mkdir -p libgeoip-1.0.3; cp -rp ebin priv libgeoip-1.0.3

Comments (0)

Files changed (17)

 dist/
 src/geoipport
 libgeoip-*
+priv/
+ebin/
+c_src/*.o
 who:   matt@genges.com
 what:  erlang port for libgeoip.  supports GeoIPCity and GeoLiteCity DBs.
 when:  made December 2008. published December 2008.
+when2: Last updated October 2010.  Rebar-ized and added license.
 where: everywhere
 why:   I don't like having to do external service calls for geo lookups.
 
                egeoip: 5498 lookups per second
   resulting in libgeoip-erlang being about 28% faster than egeoip.
 
-installation: build it.  
-              edit src/Makefile if erlang isn't in /usr/local/lib/erlang
-              put everything in dist/ where you need it to be
+Building Instructions:
+         > rebar compile
+         You may need to edit rebar.config if your maxmind headers and
+         objects aren't in /opt/local.
+
+Note: You must have the libgeoip directory name *in* your path for libgeoip to
+find the correct port to run.
 
 usage [APPLICATION]:
-% erl -pz ./libgeoip-erlang/libgeoip-VER/ebin/
+> erl -pz ../ligeoip-erlang/ebin -pz ../libgeoip-erlang/priv
 
 Eshell V5.6.5  (abort with ^G)
 1> application:start(libgeoip_app).
 
 
 usage [DIRECT]:
-% erl -pz ./libgeoip-erlang/libgeoip-1.0/ebin/
+> erl -pz ../libgeoip-erlang/ebin -pz ../libgeoip-erlang/priv
 
 Eshell V5.6.5  (abort with ^G)
 1> libgeoip:start_link("/usr/local/maxmind/data/GeoLiteCity.dat").
+.PHONY: dist clean 
+
+## - if you have multiple versions, pick the highest one
+OTP_LIB_DIR:= $(shell erl -noshell -eval 'io:format("~s", [code:lib_dir()])' -s erlang halt)
+EI=$(shell ls -d $(OTP_LIB_DIR)/erl_interface* | sort -n |tail -1)
+LIBGEOIP=../priv/libgeoip
+
+## - default macports install location for libgeoip
+GEOIP_LIBS=/opt/local
+
+dist: $(LIBGEOIP)
+
+$(LIBGEOIP): geoipport.c erl_comm.c geohash.c Makefile
+	@mkdir -p ../priv
+	gcc -Wall -Os -L$(GEOIP_LIBS)/lib -I$(GEOIP_LIBS)/include \
+-L$(EI)/lib/ -I$(EI)/include/ \
+-o $(LIBGEOIP) erl_comm.c geoipport.c geohash.c -lerl_interface -lei -lGeoIP \
+-lpthread
+
+## - Comments are fun
+
+clean:
+	rm -f $(LIBGEOIP)
+/* mostly from http://erlang.org/doc/tutorial/erl_comm.c */
+
+/* erl_comm.c */
+#include <sys/types.h>
+#include <sys/uio.h>
+#include <unistd.h>
+
+typedef unsigned char byte;
+
+int read_exact(byte *, int);
+int write_exact(byte *, int);
+
+int read_cmd(byte *buf)
+{
+  int len;
+
+  if (read_exact(buf, 2) != 2)
+    return(-1);
+  len = (buf[0] << 8) | buf[1];
+  return read_exact(buf, len);
+}
+
+int write_cmd(byte *buf, int len)
+{
+  byte li;
+
+  li = (len >> 8) & 0xff;
+  write_exact(&li, 1);
+  
+  li = len & 0xff;
+  write_exact(&li, 1);
+
+  return write_exact(buf, len);
+}
+
+int read_exact(byte *buf, int len)
+{
+  int i, got=0;
+
+  do {
+    if ((i = read(0, buf+got, len-got)) <= 0)
+      return(i);
+    got += i;
+  } while (got<len);
+
+  return(len);
+}
+
+int write_exact(byte *buf, int len)
+{
+  int i, wrote = 0;
+
+  do {
+    if ((i = write(1, buf+wrote, len-wrote)) <= 0)
+      return (i);
+    wrote += i;
+  } while (wrote<len);
+
+  return (len);
+}
+/* 
+ | direct JS -> C port of encodeGeoHash (ugly formatting and all) from:
+ | http://github.com/davetroy/geohash-js/tree/master/geohash.js
+ | no decoding or adjacency finding implemented (or required) yet
+ | geohash.js is MIT licensed as is this translation of it.
+ */
+
+int BITS[] = {16, 8, 4, 2, 1};
+char BASE32[] = "0123456789bcdefghjkmnpqrstuvwxyz";
+
+void encodeGeoHash(float latitude, float longitude, char *geohash_buf) {
+        int is_even=1;
+        double lat[4] = {0};
+        double lon[4] = {0};
+        int bit=0;
+        int ch=0;
+        int precision = 12;  /* static precision. no dynamic short fanciness */
+        int geohash_length = 0;
+        double mid = 0;
+
+        lat[0] = -90.0;  lat[1] = 90.0;
+        lon[0] = -180.0; lon[1] = 180.0;
+
+        while (geohash_length < precision) {
+          if (is_even) {
+                        mid = (lon[0] + lon[1]) / 2;
+            if (longitude > mid) {
+                                ch |= BITS[bit];
+                                lon[0] = mid;
+            } else
+                                lon[1] = mid;
+          } else {
+                        mid = (lat[0] + lat[1]) / 2;
+            if (latitude > mid) {
+                                ch |= BITS[bit];
+                                lat[0] = mid;
+            } else
+                                lat[1] = mid;
+          }
+
+                is_even = !is_even;
+          if (bit < 4)
+                        bit++;
+          else {
+                        geohash_buf[geohash_length] = BASE32[ch];
+                        geohash_length++;
+                        bit = 0;
+                        ch = 0;
+          }
+        }
+}
+

Binary file added.

c_src/geoipport.c

+#include "GeoIP.h"
+#include "GeoIPCity.h"
+#include "erl_interface.h"
+
+typedef unsigned char byte;
+
+#define fprintf(a,b, ...) 
+//#define fprintf(a,b, ...) fprintf(a, b, __VA_ARGS__)
+
+#define erl_mk_string(x) erl_mk_string(((x) == NULL) ? "" : x)
+
+int read_cmd(byte *);
+int write_cmd(byte *, int);
+void encodeGeoHash(float, float, char *);
+
+void lookup_city(GeoIP *geoip, unsigned int ip) {
+  ETERM *country_code, *region, *state, *city, *postal_code, *lat, *lng, *rec;
+  ETERM *erl_geohash;
+  ETERM *tuplep;
+  ETERM *result_arr[9] = {0};
+  GeoIPRecord *gir = NULL;
+  char geohash[16] = {0}; /* auto null terminate the geohash string */
+  int tuple_len = 0;
+  byte buffer[1024] = {0};
+
+  gir = GeoIP_record_by_ipnum(geoip, ip);
+  if (gir != NULL) {
+fprintf(stderr, "NOT NULL\n");
+fprintf(stderr, "values: %s %s %s %s %s\n", gir->country_code, gir->region, gir->city, gir->postal_code, geohash);
+    country_code = erl_mk_string(gir->country_code);
+    region       = erl_mk_string(gir->region);
+    state        = erl_mk_string(GeoIP_region_name_by_code(gir->country_code, gir->region)),
+    city         = erl_mk_string(gir->city);
+    postal_code  = erl_mk_string(gir->postal_code);
+    lat          = erl_mk_float(gir->latitude);
+    lng          = erl_mk_float(gir->longitude);
+    encodeGeoHash(gir->latitude, gir->longitude, geohash);
+    erl_geohash  = erl_mk_string(geohash);
+    rec = erl_mk_atom("geoip");
+    result_arr[0] = rec;
+    result_arr[1] = country_code;
+    result_arr[2] = region;
+    result_arr[3] = state;
+    result_arr[4] = city;
+    result_arr[5] = postal_code;
+    result_arr[6] = lat;
+    result_arr[7] = lng;
+    result_arr[8] = erl_geohash;
+    tuplep = erl_mk_tuple(result_arr, 9);
+  }
+  else {
+    tuplep = erl_mk_string("");
+  }
+
+  tuple_len = erl_encode(tuplep, buffer);
+  write_cmd(buffer, tuple_len);
+  erl_free_compound(tuplep); 
+
+  if (gir != NULL) GeoIPRecord_delete(gir);
+}
+
+
+
+int main() {
+  ETERM *ipp = NULL;
+  GeoIP *gi = NULL;
+  byte buf[512] = {0};  /* big enough */
+  void (*lookup)(GeoIP *, unsigned int) = NULL;
+  unsigned long allocated, freed;
+
+  erl_init(NULL, 0);
+
+  while (read_cmd(buf) > 0) {
+
+    erl_eterm_statistics(&allocated, &freed);
+    fprintf(stderr, "Allocated: %lu, Freed: %lu\n", allocated, freed);
+    if ((ipp = erl_decode(buf)) == NULL) {
+      ETERM *err = erl_mk_atom("decode_error");
+      int len = erl_encode(err, buf);
+      write_cmd(buf, len);
+      erl_free_compound(err);
+    }
+
+    if (ERL_IS_UNSIGNED_INTEGER(ipp) != 0) {
+    fprintf(stderr, "IS UNSIGNED INTEGER\n");
+      (*lookup)(gi, ERL_INT_UVALUE(ipp));
+    }
+    else if (ERL_IS_INTEGER(ipp) != 0) {
+    fprintf(stderr, "IS INTEGER\n");
+      (*lookup)(gi, ERL_INT_VALUE(ipp));
+    }
+    else if (ERL_IS_LIST(ipp) != 0) {
+    fprintf(stderr, "IS LIST\n");
+      char *filename = erl_iolist_to_string(ipp);
+      byte edition;
+      if (gi) GeoIP_delete(gi);
+      /* mmap the data file.  
+           or GEOIP_MEMORY_CACHE to load the entire 15MB file all at once */
+      gi = GeoIP_open(filename, GEOIP_MMAP_CACHE); 
+      if (gi == NULL) {
+        fprintf(stderr, "Error opening database\n");
+        exit(1);
+      }
+     
+      edition = GeoIP_database_edition(gi);
+      if (edition == GEOIP_CITY_EDITION_REV0 || 
+          edition == GEOIP_CITY_EDITION_REV1) {
+        lookup = &lookup_city;
+      }
+      erl_free(filename);
+    }
+    else {
+      ETERM *err = erl_mk_atom("bad_type");
+      int len = erl_encode(err, buf);
+      write_cmd(buf, len);
+      erl_free_compound(err);
+    }
+    erl_free_compound(ipp);
+  }
+  fprintf(stderr, "LEAVING\n");
+  GeoIP_delete(gi);
+
+  return 0;
+}

include/libgeoip.hrl

+-record(geoip, {country, region, state, city, postal, lat, lng, geohash}).
+{port_envs, [{"CFLAGS", "-Wall -I/opt/local/include "},
+             {"LDFLAGS", "-L/opt/local/lib "}]}.
+
+% This is a hack because I can't make rebar compile non-linked-in drivers.
+{port_pre_script, {"gmake -C c_src", "priv/libgeoip"}}.
+{port_cleanup_script, "gmake -C c_src clean"}.

src/Makefile

-.PHONY: dist clean 
-
-## - if you have multiple versions, pick the highest one
-OTP_LIB_DIR:= $(shell erl -noshell -eval 'io:format("~s", [code:lib_dir()])' -s erlang halt)
-EI=$(shell ls -d $(OTP_LIB_DIR)/erl_interface* | sort -n |tail -1)
-VER=1.0.1
-DIST=../libgeoip-$(VER)
-LIBGEOIP=geoipport
-
-## - default macports install location for libgeoip
-GEOIP_LIBS=/opt/local
-
-.SUFFIXES: .beam .erl
-ERL_FILES=$(wildcard *.erl)
-DIST_ERL=$(ERL_FILES:%erl=$(DIST)/ebin/%beam)
-
-
-dist: $(DIST) $(DIST_ERL) $(LIBGEOIP) $(DIST)/ebin/libgeoip_app.app \
-$(DIST)/priv/$(LIBGEOIP) $(DIST)/include/libgeoip.hrl
-
-## - Setting up directory structure
-
-$(DIST):
-	mkdir -p $(DIST)/{src,ebin,priv,include}
-
-$(DIST)/priv/$(LIBGEOIP): $(LIBGEOIP)
-	cp $(LIBGEOIP) $(DIST)/priv
-
-$(DIST)/include/libgeoip.hrl: libgeoip.hrl
-	cp libgeoip.hrl $(DIST)/include
-
-$(DIST)/ebin/libgeoip_app.app: libgeoip_app.app
-	cp $< $(dir $@)
-
-## - Compiling erl and the port
-
-$(DIST)/ebin/%.beam: %.erl Makefile
-	erlc -W +debug_info -o $(dir $@)  $<
-	cp $< $(DIST)/src
-
-$(LIBGEOIP): geoipport.c erl_comm.c geohash.c Makefile
-	gcc -Wall -Os -L$(GEOIP_LIBS)/lib -I$(GEOIP_LIBS)/include \
--L$(EI)/lib/ -I$(EI)/include/ \
--o $(LIBGEOIP) erl_comm.c geoipport.c geohash.c -lerl_interface -lei -lGeoIP \
--lpthread
-
-## - Comments are fun
-
-clean:
-	rm -rf $(LIBGEOIP) $(DIST)

src/erl_comm.c

-/* mostly from http://erlang.org/doc/tutorial/erl_comm.c */
-
-/* erl_comm.c */
-#include <sys/types.h>
-#include <sys/uio.h>
-#include <unistd.h>
-
-typedef unsigned char byte;
-
-int read_exact(byte *, int);
-int write_exact(byte *, int);
-
-int read_cmd(byte *buf)
-{
-  int len;
-
-  if (read_exact(buf, 2) != 2)
-    return(-1);
-  len = (buf[0] << 8) | buf[1];
-  return read_exact(buf, len);
-}
-
-int write_cmd(byte *buf, int len)
-{
-  byte li;
-
-  li = (len >> 8) & 0xff;
-  write_exact(&li, 1);
-  
-  li = len & 0xff;
-  write_exact(&li, 1);
-
-  return write_exact(buf, len);
-}
-
-int read_exact(byte *buf, int len)
-{
-  int i, got=0;
-
-  do {
-    if ((i = read(0, buf+got, len-got)) <= 0)
-      return(i);
-    got += i;
-  } while (got<len);
-
-  return(len);
-}
-
-int write_exact(byte *buf, int len)
-{
-  int i, wrote = 0;
-
-  do {
-    if ((i = write(1, buf+wrote, len-wrote)) <= 0)
-      return (i);
-    wrote += i;
-  } while (wrote<len);
-
-  return (len);
-}

src/geohash.c

-/* 
- | direct JS -> C port of encodeGeoHash (ugly formatting and all) from:
- | http://github.com/davetroy/geohash-js/tree/master/geohash.js
- | no decoding or adjacency finding implemented (or required) yet
- */
-
-int BITS[] = {16, 8, 4, 2, 1};
-char BASE32[] = "0123456789bcdefghjkmnpqrstuvwxyz";
-
-void encodeGeoHash(float latitude, float longitude, char *geohash_buf) {
-        int is_even=1;
-        double lat[4] = {0};
-        double lon[4] = {0};
-        int bit=0;
-        int ch=0;
-        int precision = 12;  /* static precision. no dynamic short fanciness */
-        int geohash_length = 0;
-        double mid = 0;
-
-        lat[0] = -90.0;  lat[1] = 90.0;
-        lon[0] = -180.0; lon[1] = 180.0;
-
-        while (geohash_length < precision) {
-          if (is_even) {
-                        mid = (lon[0] + lon[1]) / 2;
-            if (longitude > mid) {
-                                ch |= BITS[bit];
-                                lon[0] = mid;
-            } else
-                                lon[1] = mid;
-          } else {
-                        mid = (lat[0] + lat[1]) / 2;
-            if (latitude > mid) {
-                                ch |= BITS[bit];
-                                lat[0] = mid;
-            } else
-                                lat[1] = mid;
-          }
-
-                is_even = !is_even;
-          if (bit < 4)
-                        bit++;
-          else {
-                        geohash_buf[geohash_length] = BASE32[ch];
-                        geohash_length++;
-                        bit = 0;
-                        ch = 0;
-          }
-        }
-}
-

src/geoipport.c

-#include "GeoIP.h"
-#include "GeoIPCity.h"
-#include "erl_interface.h"
-
-typedef unsigned char byte;
-
-#define fprintf(a,b, ...) 
-//#define fprintf(a,b, ...) fprintf(a, b, __VA_ARGS__)
-
-#define erl_mk_string(x) erl_mk_string(((x) == NULL) ? "" : x)
-
-int read_cmd(byte *);
-int write_cmd(byte *, int);
-void encodeGeoHash(float, float, char *);
-
-void lookup_city(GeoIP *geoip, unsigned int ip) {
-  ETERM *country_code, *region, *state, *city, *postal_code, *lat, *lng, *rec;
-  ETERM *erl_geohash;
-  ETERM *tuplep;
-  ETERM *result_arr[9] = {0};
-  GeoIPRecord *gir = NULL;
-  char geohash[16] = {0}; /* auto null terminate the geohash string */
-  int tuple_len = 0;
-  byte buffer[1024] = {0};
-
-  gir = GeoIP_record_by_ipnum(geoip, ip);
-  if (gir != NULL) {
-fprintf(stderr, "NOT NULL\n");
-fprintf(stderr, "values: %s %s %s %s %s\n", gir->country_code, gir->region, gir->city, gir->postal_code, geohash);
-    country_code = erl_mk_string(gir->country_code);
-    region       = erl_mk_string(gir->region);
-    state        = erl_mk_string(GeoIP_region_name_by_code(gir->country_code, gir->region)),
-    city         = erl_mk_string(gir->city);
-    postal_code  = erl_mk_string(gir->postal_code);
-    lat          = erl_mk_float(gir->latitude);
-    lng          = erl_mk_float(gir->longitude);
-    encodeGeoHash(gir->latitude, gir->longitude, geohash);
-    erl_geohash  = erl_mk_string(geohash);
-    rec = erl_mk_atom("geoip");
-    result_arr[0] = rec;
-    result_arr[1] = country_code;
-    result_arr[2] = region;
-    result_arr[3] = state;
-    result_arr[4] = city;
-    result_arr[5] = postal_code;
-    result_arr[6] = lat;
-    result_arr[7] = lng;
-    result_arr[8] = erl_geohash;
-    tuplep = erl_mk_tuple(result_arr, 9);
-  }
-  else {
-    tuplep = erl_mk_string("");
-  }
-
-  tuple_len = erl_encode(tuplep, buffer);
-  write_cmd(buffer, tuple_len);
-  erl_free_compound(tuplep); 
-
-  if (gir != NULL) GeoIPRecord_delete(gir);
-}
-
-
-
-int main() {
-  ETERM *ipp = NULL;
-  GeoIP *gi = NULL;
-  byte buf[512] = {0};  /* big enough */
-  void (*lookup)(GeoIP *, unsigned int) = NULL;
-  unsigned long allocated, freed;
-
-  erl_init(NULL, 0);
-
-  while (read_cmd(buf) > 0) {
-
-    erl_eterm_statistics(&allocated, &freed);
-    fprintf(stderr, "Allocated: %lu, Freed: %lu\n", allocated, freed);
-    if ((ipp = erl_decode(buf)) == NULL) {
-      ETERM *err = erl_mk_atom("decode_error");
-      int len = erl_encode(err, buf);
-      write_cmd(buf, len);
-      erl_free_compound(err);
-    }
-
-    if (ERL_IS_UNSIGNED_INTEGER(ipp) != 0) {
-    fprintf(stderr, "IS UNSIGNED INTEGER\n");
-      (*lookup)(gi, ERL_INT_UVALUE(ipp));
-    }
-    else if (ERL_IS_INTEGER(ipp) != 0) {
-    fprintf(stderr, "IS INTEGER\n");
-      (*lookup)(gi, ERL_INT_VALUE(ipp));
-    }
-    else if (ERL_IS_LIST(ipp) != 0) {
-    fprintf(stderr, "IS LIST\n");
-      char *filename = erl_iolist_to_string(ipp);
-      byte edition;
-      if (gi) GeoIP_delete(gi);
-      /* mmap the data file.  
-           or GEOIP_MEMORY_CACHE to load the entire 15MB file all at once */
-      gi = GeoIP_open(filename, GEOIP_MMAP_CACHE); 
-      if (gi == NULL) {
-        fprintf(stderr, "Error opening database\n");
-        exit(1);
-      }
-     
-      edition = GeoIP_database_edition(gi);
-      if (edition == GEOIP_CITY_EDITION_REV0 || 
-          edition == GEOIP_CITY_EDITION_REV1) {
-        lookup = &lookup_city;
-      }
-      erl_free(filename);
-    }
-    else {
-      ETERM *err = erl_mk_atom("bad_type");
-      int len = erl_encode(err, buf);
-      write_cmd(buf, len);
-      erl_free_compound(err);
-    }
-    erl_free_compound(ipp);
-  }
-  fprintf(stderr, "LEAVING\n");
-  GeoIP_delete(gi);
-
-  return 0;
-}

src/libgeoip.app.src

+{application, libgeoip,
+ [{description, "libgeoip port"},
+  {vsn, "1.0.2"},
+  {modules, []},
+  {registered, [libgeoip]},
+  {applications, [kernel, stdlib]},
+  {mod, {libgeoip_app,[]}}
+ ]}.
            {error, bad_name} -> "";
            Found -> Found ++ "/"
          end,
-  GeoIP = open_port({spawn, Path ++ "geoipport"}, [{packet, 2}, binary]),
+         io:format("Path is: ~p~n", [Path]),
+  GeoIP = open_port({spawn, Path ++ "libgeoip"}, [{packet, 2}, binary]),
   process_flag(trap_exit, true),
   case DBName of
     [] -> ok;

src/libgeoip.hrl

--record(geoip, {country, region, state, city, postal, lat, lng, geohash}).

src/libgeoip_app.app

-{application, libgeoip_app,
- [{description, "libgeoip port"},
-  {vsn, "1.0.1"},
-  {modules, [libgeoip_app, libgeoip_sup, libgeoip]},
-  {registered, [libgeoip]},
-  {applications, [kernel, stdlib]},
-  {mod, {libgeoip_app,[]}}
- ]}.
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.