{-# LANGUAGE OverloadedStrings #-}

module System.Cached.JSON (
  getCachedJSON,
  getCachedJSONQuery,
  lookupKey
  )
where

import Control.Monad
import Data.Aeson
import qualified Data.ByteString.Lazy.Char8 as B
import Data.Time.Clock (diffUTCTime, getCurrentTime, NominalDiffTime)
import Network.HTTP.Query
import System.Directory
import System.Environment.XDG.BaseDir
import System.FilePath

-- | If the local cached json file is new enough then use it,
-- otherwise refresh from the remote url.
getCachedJSON :: (FromJSON a, ToJSON a)
              => String -- ^ subdirectory/program name
              -> FilePath -- ^ filename
              -> String -- ^ json url
              -> NominalDiffTime -- ^ cache duration (minutes)
              -> IO a
getCachedJSON :: forall a.
(FromJSON a, ToJSON a) =>
String -> String -> String -> NominalDiffTime -> IO a
getCachedJSON String
prog String
jsonfile String
url =
  String -> String -> IO a -> NominalDiffTime -> IO a
forall a.
(FromJSON a, ToJSON a) =>
String -> String -> IO a -> NominalDiffTime -> IO a
getCachedJSONQuery String
prog String
jsonfile (String -> Query -> IO a
forall (m :: * -> *) a.
(MonadIO m, FromJSON a) =>
String -> Query -> m a
webAPIQuery String
url [])

-- | Similar to getCachedJSON but takes an IO procedure that fetches
-- the remote json data.
getCachedJSONQuery :: (FromJSON a, ToJSON a)
                   => String -- ^ program name
                   -> FilePath -- ^ filename
                   -> IO a -- ^ http query
                   -> NominalDiffTime -- ^ cache duration (minutes)
                   -> IO a
getCachedJSONQuery :: forall a.
(FromJSON a, ToJSON a) =>
String -> String -> IO a -> NominalDiffTime -> IO a
getCachedJSONQuery String
prog String
jsonfile IO a
webquery NominalDiffTime
minutes = do
  file <- String -> String -> IO String
getUserCacheFile String
prog String
jsonfile
  exists <- doesFileExist file
  unless exists $ do
    putStrLn $ "Creating " ++ file ++ " ..."
    createDirectoryIfMissing True (takeDirectory file)
  recent <- do
    if exists
      then do
      size <- getFileSize file
      if size == 0
        then return False
        else do
        ts <- getModificationTime file
        t <- getCurrentTime
        return $ diffUTCTime t ts < (minutes * 60)
      else return False
  if recent
    then do
    eObj <- eitherDecode <$> B.readFile file
    case eObj of
      Left String
err -> String -> IO a
forall a. HasCallStack => String -> a
error String
err
      Right a
obj -> a -> IO a
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return a
obj
    else do
    obj <- webquery
    B.writeFile file $ encode obj
    return obj