packages feed

snaplet-tasks-0.1: src/Snap/Snaplet/Tasks/Internal.hs

module Snap.Snaplet.Tasks.Internal where

import           System( getArgs )
import           System.Exit

import           Snap.Snaplet.Tasks.Types

import           Control.Concurrent

import qualified Data.ByteString.Char8 as B
import           Data.List (intercalate)
import qualified Data.Map as Map
import qualified Data.List.Utils as U
import           Data.Hash

import           Network.Curl

-- | This function reads options from command line
--   and if it sees any options after "T" it
--   tries to compile them into task.
taskFromCommandLine :: IO (Maybe Task)
taskFromCommandLine = do
  taskArgs <- return . tail . dropWhile ((/=) "T") =<< getArgs
  case null taskArgs of
    True  -> return Nothing
    False -> return $ Just $ taskForArgs taskArgs
  where
    taskForArgs :: [String] -> Task
    taskForArgs args = Task
      (namespace heading)
      (name      heading)
      (taskargs  restargs)
      portFromArgs
      where
        heading :: [String]
        heading = U.split ":" $ head args

        portFromArgs :: String
        portFromArgs =
          case dropWhile ((/=) "-p") args of
            [] -> "8000"
            xs -> head $ tail xs

        restargs :: [(B.ByteString, B.ByteString)]
        restargs = map toKeyValue $ takeWhile ((/=) "-p") $ tail args
 
        toKeyValue :: String -> (B.ByteString, B.ByteString)
        toKeyValue s = (sp !! 0, sp !! 1)
         where
          sp = map B.pack $ U.split "=" s

        namespace :: [String] -> String
        namespace = head

        name :: [String] -> String
        name = (intercalate "/") . tail

        taskargs :: [(B.ByteString, B.ByteString)] -> Map.Map B.ByteString B.ByteString
        taskargs = Map.fromList

-- | This function lives in different thread than main app thread
--   and for given task it waits until app is ready to serve requests 
--   and then it fires request described in that task.
--   
--   For example if you had a task with namespace 'supercool' and
--   name 'task' and params id=1234 number=12345 url=google.com
--   then it'd fire PUT request for url: 
--   'localhost:[port]/supercool/task?id=1234&number=12345&url=google.com'
--   If 404 occurs it yells at user that such task does not exist.
--   For task name 'task:cool' url would start like:
--   'localhost/supercool/task/cool?id....' 
runTask :: Task -> IO ()
runTask task = waitForApp >> requestTask
  where
    waitForApp = do
      (code, _) <- curlGetString "http://localhost" 
                     [CurlPort $ read $ taskPort task]
      case code of
        CurlOK -> return ()
        _      -> threadDelay 100 >> waitForApp

    requestTask = do
      resp <- curlGetResponse taskUrl 
                        [CurlPort $ read $ taskPort task]
      case respCurlCode resp of
        CurlOK -> putStrLn   "Task ended successfully"
        _      -> 
          case respStatus resp of
            404 -> putStrLn   "No such task in application"
            _   -> putStrLn $ "Task request ended with HTTP code " ++
                                (show $ respStatus resp)

    taskUrl :: URLString
    taskUrl = "http://localhost/" ++ (taskNamespace task) ++
               "/" ++ nameForTask ++ argsToUrl

    nameForTask :: String
    nameForTask = hashString $ taskName task

    argsToUrl :: String
    argsToUrl = 
      case Map.null $ taskArgs task of
        True  -> ""
        False -> "?" ++ (intercalate "&" $ map toArg $ Map.toList $ taskArgs task)
    
    toArg :: (B.ByteString, B.ByteString) -> String
    toArg (k, v) = (B.unpack k) ++ "=" ++ (B.unpack v)

-- | This function takes a string and returns a hash for it
hashString :: String -> String
hashString s = show $ asWord64 $ foldl combine (hashInt $ length s) $ map hash s