packages feed

geoip2 0.1.0.2 → 0.1.0.3

raw patch · 5 files changed

+76/−59 lines, 5 filesdep +lrucachedep +mmapdep −bytestring-mmapdep ~basePVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies added: lrucache, mmap

Dependencies removed: bytestring-mmap

Dependency ranges changed: base

API changes (from Hackage documentation)

- Data.GeoIP2: geoCity :: GeoResult -> Maybe Text
- Data.GeoIP2: geoContinent :: GeoResult -> Maybe Text
- Data.GeoIP2: geoContinentCode :: GeoResult -> Maybe Text
- Data.GeoIP2: geoCountry :: GeoResult -> Maybe Text
- Data.GeoIP2: geoCountryISO :: GeoResult -> Maybe Text
- Data.GeoIP2: geoLocation :: GeoResult -> Maybe (Double, Double)
- Data.GeoIP2: geoSubdivisions :: GeoResult -> [(Text, Text)]
- Data.GeoIP2: instance Eq GeoIP
- Data.GeoIP2: instance Eq GeoResult
- Data.GeoIP2: instance Show GeoIP
- Data.GeoIP2: instance Show GeoResult
+ Data.GeoIP2: [geoCity] :: GeoResult -> Maybe Text
+ Data.GeoIP2: [geoContinentCode] :: GeoResult -> Maybe Text
+ Data.GeoIP2: [geoContinent] :: GeoResult -> Maybe Text
+ Data.GeoIP2: [geoCountryISO] :: GeoResult -> Maybe Text
+ Data.GeoIP2: [geoCountry] :: GeoResult -> Maybe Text
+ Data.GeoIP2: [geoLocation] :: GeoResult -> Maybe (Double, Double)
+ Data.GeoIP2: [geoSubdivisions] :: GeoResult -> [(Text, Text)]
+ Data.GeoIP2: instance GHC.Classes.Eq Data.GeoIP2.GeoIP
+ Data.GeoIP2: instance GHC.Classes.Eq Data.GeoIP2.GeoResult
+ Data.GeoIP2: instance GHC.Show.Show Data.GeoIP2.GeoIP
+ Data.GeoIP2: instance GHC.Show.Show Data.GeoIP2.GeoResult

Files

ChangeLog.md view
@@ -1,3 +1,7 @@+# 0.1.0.2 -> 0.1.0.3+- Changed bytestring-mmap to mmap+- Add LRU caching to improve performance (10000 entries are cached)+ # 0.1.0.1 -> 0.1.0.2   - GHC-7.10 compatibiliy
Data/GeoIP2.hs view
@@ -1,6 +1,6 @@-{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE MultiWayIf          #-}+{-# LANGUAGE OverloadedStrings   #-} {-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE MultiWayIf #-}  module Data.GeoIP2 (   -- * Library description@@ -23,32 +23,35 @@   , GeoResult(..) ) where -import Control.Monad (when, unless)-import System.IO.Posix.MMap-import qualified Data.ByteString as BS-import qualified Data.ByteString.Lazy as BL-import Data.Int-import Data.Binary-import qualified Data.Text as T-import Data.IP (IP(..), ipv4ToIPv6)-import Control.Applicative ((<$>), (<*>))-import qualified Data.Map as Map-import Data.Maybe (mapMaybe, fromMaybe)+import           Control.Applicative    ((<$>), (<*>))+import           Control.Monad          (unless, when)+import           Data.Binary+import qualified Data.ByteString        as BS+import qualified Data.ByteString.Lazy   as BL+import qualified Data.Cache.LRU.IO      as LRU+import           Data.Int+import           Data.IP                (IP (..), ipv4ToIPv6)+import qualified Data.Map               as Map+import           Data.Maybe             (fromMaybe, mapMaybe)+import qualified Data.Text              as T+import           System.IO.MMap+import           System.IO.Unsafe       (unsafePerformIO) -import Data.GeoIP2.Fields-import Data.GeoIP2.SearchTree+import           Data.GeoIP2.Fields+import           Data.GeoIP2.SearchTree  -- | Address type stored in database data GeoIP = GeoIPv6 | GeoIPv4 deriving (Eq, Show)  -- | Handle for search operations data GeoDB = GeoDB {-   geoMem :: BL.ByteString- , geoDbType :: T.Text         -- ^ String that indicates the structure of each data record associated with an IP address- , geoDbLanguages :: [T.Text]  -- ^ Languages supported in database- , geoDbNodeCount :: Int64- , geoDbRecordSize :: Int- , geoDbAddrType :: GeoIP -- ^ Type of address (IPv4/IPv6) stored in a database+   geoMem           :: BS.ByteString+ , geoLru           :: LRU.AtomicLRU Int GeoField+ , geoDbType        :: T.Text         -- ^ String that indicates the structure of each data record associated with an IP address+ , geoDbLanguages   :: [T.Text]  -- ^ Languages supported in database+ , geoDbNodeCount   :: Int64+ , geoDbRecordSize  :: Int+ , geoDbAddrType    :: GeoIP -- ^ Type of address (IPv4/IPv6) stored in a database  , geoDbDescription :: Maybe T.Text -- ^ Description of a database in english } @@ -63,12 +66,12 @@ -- | Open database, mmap it into memory, parse header and return a handle for search operations openGeoDB :: FilePath -> IO GeoDB openGeoDB geoFile = do-    bsmem <- unsafeMMapFile geoFile-    let (DataMap hdr) = decode (BL.fromChunks [getHeaderBytes bsmem])-        mem = BL.fromChunks [bsmem]+    bsmem <- mmapFileByteString geoFile Nothing+    let (DataMap hdr) = decode (BL.fromStrict $ getHeaderBytes bsmem)     when (hdr .: "binary_format_major_version" /= (2 :: Int)) $ error "Unsupported database version, only v2 supported."     unless (hdr .: "record_size" `elem` [24, 28, 32 :: Int]) $ error "Record size not supported."-    return $ GeoDB mem (hdr .: "database_type")+    lru <- LRU.newAtomicLRU (Just 10000)+    return $ GeoDB bsmem lru (hdr .: "database_type")                        (fromMaybe [] $ hdr .:? "languages")                        (hdr .: "node_count") (hdr .: "record_size")                        (if (hdr .: "ip_version") == (4 :: Int) then GeoIPv4 else GeoIPv6)@@ -79,35 +82,46 @@ rawGeoData :: Monad m => GeoDB -> IP -> m GeoField rawGeoData geodb addr = do   bits <- coerceAddr-  offset <- getDataOffset (geoMem geodb, geoDbNodeCount geodb, geoDbRecordSize geodb) bits-  let basedata = dataAt offset-  return $ resolvePointers basedata+  offset <- fromIntegral <$> getDataOffset (geoMem geodb, geoDbNodeCount geodb, geoDbRecordSize geodb) bits+  basedata <- dataAt offset+  resolvePointers basedata   where-    dataSectionStart = fromIntegral (geoDbRecordSize geodb `div` 4) * geoDbNodeCount geodb + 16-    dataAt offset = decode (BL.drop (offset + dataSectionStart) (geoMem geodb))+    dataSectionStart = (geoDbRecordSize geodb `div` 4) * fromIntegral (geoDbNodeCount geodb) + 16+    -- Add caching+    dataAt offset = case lruget offset of+                      Right res -> return res+                      Left err -> fail err+    lruget offset = unsafePerformIO $ do+        cached <- LRU.lookup offset (geoLru geodb)+        case cached of+          Just result -> return (Right result)+          Nothing -> do+            let decres = decodeOrFail (BL.fromStrict $ BS.drop (offset + dataSectionStart) (geoMem geodb))+            case decres of+              Left (_,_,err) -> return (Left err)+              Right (_,_,result) -> do+                LRU.insert offset result (geoLru geodb)+                return (Right result)     coerceAddr       | (IPv4 _) <- addr, GeoIPv4 <- geoDbAddrType geodb = return $ ipToBits addr       | (IPv6 _) <- addr, GeoIPv6 <- geoDbAddrType geodb = return $ ipToBits addr       | (IPv4 addrv4) <- addr, GeoIPv6 <- geoDbAddrType geodb = return $ ipToBits $ IPv6 (ipv4ToIPv6 addrv4)       | otherwise = fail "Cannot search IPv6 address in IPv4 database"-    resolvePointers (DataPointer ptr) = resolvePointers $ dataAt ptr-    resolvePointers (DataMap obj) = DataMap $ Map.fromList $ map resolveTuple (Map.toList obj)-    resolvePointers (DataArray arr) = DataArray $ map resolvePointers arr-    resolvePointers x = x-    resolveTuple (a,b) =-        let r1 = resolvePointers a-            r2 = resolvePointers b-        in (r1, r2)+    resolvePointers (DataPointer ptr) = dataAt (fromIntegral ptr) >>= resolvePointers -- TODO - limit recursion?+    resolvePointers (DataMap obj) = DataMap . Map.fromList <$> mapM resolveTuple (Map.toList obj)+    resolvePointers (DataArray arr) = DataArray <$> mapM resolvePointers arr+    resolvePointers x = return x+    resolveTuple (a,b) = (,) <$> resolvePointers a <*> resolvePointers b  -- | Result of a search query data GeoResult = GeoResult {-    geoContinent :: Maybe T.Text+    geoContinent     :: Maybe T.Text   , geoContinentCode :: Maybe T.Text-  , geoCountryISO :: Maybe T.Text-  , geoCountry :: Maybe T.Text-  , geoLocation :: Maybe (Double, Double)-  , geoCity :: Maybe T.Text-  , geoSubdivisions :: [(T.Text, T.Text)]+  , geoCountryISO    :: Maybe T.Text+  , geoCountry       :: Maybe T.Text+  , geoLocation      :: Maybe (Double, Double)+  , geoCity          :: Maybe T.Text+  , geoSubdivisions  :: [(T.Text, T.Text)] } deriving (Show, Eq)  -- | Search GeoIP database, monadic version (e.g. use with Maybe or Either)
Data/GeoIP2/Fields.hs view
@@ -1,21 +1,22 @@ {-# OPTIONS_HADDOCK hide #-}-{-# LANGUAGE MultiWayIf #-} {-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiWayIf        #-}  module Data.GeoIP2.Fields where  import           Control.Applicative  ((<$>))-import           Control.Monad        (replicateM, replicateM_)+import           Control.Monad        (replicateM) import           Data.Binary import           Data.Binary.Get import           Data.Bits            (shift, (.&.))+import qualified Data.ByteString      as BS import           Data.Int import qualified Data.Map             as Map import           Data.Maybe           (fromMaybe) import           Data.ReinterpretCast (wordToDouble) import qualified Data.Text            as T import           Data.Text.Encoding   (decodeUtf8)-import           Data.Word ()+import           Data.Word            ()  data GeoField =     DataPointer Int64@@ -76,8 +77,8 @@ -- | Parse number of given length parseNumber :: Num a => Int64 -> Get a parseNumber fsize = do-  bytes <- map fromIntegral <$> replicateM (fromIntegral fsize) getWord8-  return $ foldl (\acc new -> new + 256 * acc) 0 bytes+  bytes <- getByteString (fromIntegral fsize)+  return $ BS.foldl' (\acc new -> fromIntegral new + 256 * acc) 0 bytes  instance Binary GeoField where   put = undefined@@ -118,5 +119,5 @@         11 -> DataArray <$> replicateM (fromIntegral fsize) get         14 -> return $ DataBool (fsize == 0)         _ -> do-          replicateM_ (fromIntegral fsize) getWord8+          _ <- getByteString (fromIntegral fsize)           return $ DataUnknown ftype fsize
Data/GeoIP2/SearchTree.hs view
@@ -5,7 +5,6 @@ import           Data.Binary import           Data.Bits            (shift, testBit, (.&.), (.|.)) import qualified Data.ByteString      as BS-import qualified Data.ByteString.Lazy as BL import           Data.Int import           Data.IP              (IP (..), fromIPv4, fromIPv6b) @@ -19,14 +18,12 @@ ipToBits (IPv6 addr) = concatMap byteToBits (fromIPv6b addr)  -- | Read node (2 records) given the index of a node-readNode :: BL.ByteString -> Int -> Int64 -> (Int64, Int64)+readNode :: BS.ByteString -> Int -> Int64 -> (Int64, Int64) readNode mem recordbits index =   let     bytecount = fromIntegral $ recordbits `div` 4-    bytes = BL.take (fromIntegral bytecount) $ BL.drop (fromIntegral $ index * bytecount) mem-    numbers = concatMap BS.unpack (BL.toChunks bytes) :: [Word8]-    makenum = foldl (\acc new -> fromIntegral new + 256 * acc) 0 :: [Word8] -> Word64-    num = makenum numbers+    bytes = BS.take (fromIntegral bytecount) $ BS.drop (fromIntegral $ index * bytecount) mem+    num = BS.foldl' (\acc new -> fromIntegral new + 256 * acc) 0 bytes :: Word64     -- 28 bits has a strange record format     left28 = num `shift` (-32) .|. (num .&. 0xf0000000)   in case recordbits of@@ -34,7 +31,7 @@       _  -> (fromIntegral (num `shift` negate recordbits), fromIntegral (num .&. ((1 `shift` recordbits) - 1)))  -- | Get offset in the Data Section-getDataOffset :: Monad m => (BL.ByteString, Int64, Int) -> [Bool] -> m Int64+getDataOffset :: Monad m => (BS.ByteString, Int64, Int) -> [Bool] -> m Int64 getDataOffset (mem, nodeCount, recordSize) startbits =   getnode startbits 0   where
geoip2.cabal view
@@ -1,5 +1,5 @@ name:                geoip2-version:             0.1.0.2+version:             0.1.0.3 synopsis:            Pure haskell interface to MaxMind GeoIP database description:   GeoIP2 is a haskell binding to the MaxMind GeoIP2 database.@@ -24,12 +24,13 @@   exposed-modules:     Data.GeoIP2   other-modules:       Data.GeoIP2.Fields, Data.GeoIP2.SearchTree   build-depends:       base >=4.7 && <4.9-                     , bytestring-mmap+                     , mmap                      , bytestring                      , binary                      , text                      , containers                      , iproute(>=1.4.0)                      , reinterpret-cast+                     , lrucache   default-language:    Haskell2010   ghc-options:         -Wall