packages feed

hbeanstalk 0.1 → 0.2

raw patch · 3 files changed

+219/−196 lines, 3 filesdep +attoparsecdep +blaze-builderdep +bytestringdep −HsSyckdep −parsecPVP ok

version bump matches the API change (PVP)

Dependencies added: attoparsec, blaze-builder, bytestring, network-bytestring

Dependencies removed: HsSyck, parsec

API changes (from Hackage documentation)

+ Network.Beanstalk: isDrainingException :: BeanstalkException -> Bool
- Network.Beanstalk: Job :: Int -> String -> Job
+ Network.Beanstalk: Job :: Int -> ByteString -> Job
- Network.Beanstalk: ignoreTube :: BeanstalkServer -> String -> IO Int
+ Network.Beanstalk: ignoreTube :: BeanstalkServer -> ByteString -> IO Int
- Network.Beanstalk: jobCountWithState :: BeanstalkServer -> String -> [JobState] -> IO Int
+ Network.Beanstalk: jobCountWithState :: BeanstalkServer -> ByteString -> [JobState] -> IO Int
- Network.Beanstalk: job_body :: Job -> String
+ Network.Beanstalk: job_body :: Job -> ByteString
- Network.Beanstalk: listTubeUsed :: BeanstalkServer -> IO String
+ Network.Beanstalk: listTubeUsed :: BeanstalkServer -> IO ByteString
- Network.Beanstalk: listTubes :: BeanstalkServer -> IO [String]
+ Network.Beanstalk: listTubes :: BeanstalkServer -> IO [ByteString]
- Network.Beanstalk: listTubesWatched :: BeanstalkServer -> IO [String]
+ Network.Beanstalk: listTubesWatched :: BeanstalkServer -> IO [ByteString]
- Network.Beanstalk: pauseTube :: BeanstalkServer -> String -> Int -> IO ()
+ Network.Beanstalk: pauseTube :: BeanstalkServer -> ByteString -> Int -> IO ()
- Network.Beanstalk: printList :: [String] -> IO ()
+ Network.Beanstalk: printList :: [ByteString] -> IO ()
- Network.Beanstalk: printStats :: Map String String -> IO ()
+ Network.Beanstalk: printStats :: Map ByteString ByteString -> IO ()
- Network.Beanstalk: putJob :: BeanstalkServer -> Int -> Int -> Int -> String -> IO (JobState, Int)
+ Network.Beanstalk: putJob :: BeanstalkServer -> Int -> Int -> Int -> ByteString -> IO (JobState, Int)
- Network.Beanstalk: statsJob :: BeanstalkServer -> Int -> IO (Map String String)
+ Network.Beanstalk: statsJob :: BeanstalkServer -> Int -> IO (Map ByteString ByteString)
- Network.Beanstalk: statsServer :: BeanstalkServer -> IO (Map String String)
+ Network.Beanstalk: statsServer :: BeanstalkServer -> IO (Map ByteString ByteString)
- Network.Beanstalk: statsTube :: BeanstalkServer -> String -> IO (Map String String)
+ Network.Beanstalk: statsTube :: BeanstalkServer -> ByteString -> IO (Map ByteString ByteString)
- Network.Beanstalk: useTube :: BeanstalkServer -> String -> IO ()
+ Network.Beanstalk: useTube :: BeanstalkServer -> ByteString -> IO ()
- Network.Beanstalk: watchTube :: BeanstalkServer -> String -> IO Int
+ Network.Beanstalk: watchTube :: BeanstalkServer -> ByteString -> IO Int

Files

Network/Beanstalk.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE DeriveDataTypeable, OverloadedStrings #-} ----------------------------------------------------------------------------- -- | -- Module      :  Network.Beanstalk@@ -24,24 +24,30 @@   -- * Exception Predicates   isNotFoundException, isBadFormatException, isTimedOutException,   isOutOfMemoryException, isInternalErrorException, isJobTooBigException,-  isDeadlineSoonException, isNotIgnoredException,+  isDeadlineSoonException, isNotIgnoredException, isDrainingException,   -- * Data Types   Job(..), BeanstalkServer, JobState(..), BeanstalkException(..)   ) where  import Data.Bits-import Network.Socket-import Network.BSD+import Network.Socket hiding (send, sendTo, recv, recvFrom)+import Network.Socket.ByteString import Data.List import System.IO-import Text.ParserCombinators.Parsec-import Data.Yaml.Syck import Data.Typeable import qualified Data.Map as M import qualified Control.Exception as E import Data.Maybe import Control.Monad import Control.Concurrent.MVar+import Control.Applicative hiding (many)+import Data.Attoparsec as P+import qualified Data.Attoparsec.Char8 as P8+import qualified Data.ByteString.Char8 as B+import Blaze.ByteString.Builder+import Blaze.ByteString.Builder.Char8+import Blaze.ByteString.Builder.ByteString+import Data.Monoid (mappend)  -- | Beanstalk Server, wrapped in an 'MVar' for synchronizing access --   to the server socket.  As many of these can be created as are@@ -55,7 +61,7 @@     Job { -- | Job numeric identifier           job_id :: Int,           -- | Job body-          job_body :: String}+          job_body :: B.ByteString}            deriving (Show, Read, Eq)  -- | States describing the lifecycle of a job.@@ -184,17 +190,19 @@              --   'buryJob', or 'touchJob' command being run, the job              --   will be placed back on the ready queue.  The minimum              --   value is 1.-       -> String -- ^ Job body.+       -> B.ByteString -- ^ Job body.        -> IO (JobState, Int) -- ^ State of the newly created job and its ID putJob bs priority delay ttr job_body = withMVar bs task     where task s =-              do let job_size = length job_body-                 send s ("put " ++-                         (show priority) ++ " " ++-                         (show delay) ++ " " ++-                         (show ttr) ++ " " ++-                         (show job_size) ++ "\r\n")-                 send s (job_body ++ "\r\n")+              do let job_size = B.length job_body+                 sendAll s $ toByteString (fromByteString "put " `mappend`+                                           fromShow priority `mappend` fromChar ' ' `mappend`+                                           fromShow delay `mappend` fromChar ' ' `mappend`+                                           fromShow ttr `mappend` fromChar ' ' `mappend`+                                           fromShow job_size `mappend`+                                           fromByteString "\r\n" `mappend`+                                           fromByteString job_body `mappend`+                                           fromByteString "\r\n")                  response <- readLine s                  checkForBeanstalkErrors response                  let (state, jobid) = parsePut response@@ -207,18 +215,18 @@            -> IO Job -- ^ Job reserved by this client reserveJob bs = withMVar bs task     where task s =-              do send s "reserve\r\n"+              do sendAll s "reserve\r\n"                  response <- readLine s                  checkForBeanstalkErrors response                  let (jobid, bytes) = parseReserve response-                 (jobContent, bytesRead) <- recvLen s (bytes)-                 recv s 2 -- Ending CRLF-                 return (Job (read jobid) jobContent)+                 jobContent <- recvBytes s bytes+                 recvBytes s 2 -- Ending CRLF+                 return (Job jobid jobContent)  -- | Reserve a job from the watched tube list, blocking for the specified number -- of seconds or until a job is returned.  If no jobs are found before the--- timeout value, a TimedOutException will be thrown.  If another reserved job--- is about to exceed its time-to-run, a DeadlineSoonException will be thrown.+-- timeout value, a 'TimedOutException' will be thrown.  If another reserved job+-- is about to exceed its time-to-run, a 'DeadlineSoonException' will be thrown. reserveJobWithTimeout :: BeanstalkServer -- ^ Beanstalk server                       -> Int -- ^ Time in seconds to wait for a job                             --   to become available.  Once this time@@ -227,13 +235,15 @@                       -> IO Job -- ^ Job reserved by this client reserveJobWithTimeout bs seconds = withMVar bs task     where task s =-              do send s ("reserve-with-timeout "++(show seconds)++"\r\n")+              do sendAll s $ toByteString (fromByteString "reserve-with-timeout " `mappend`+                                           fromShow seconds `mappend`+                                           fromByteString "\r\n")                  response <- readLine s                  checkForBeanstalkErrors response                  let (jobid, bytes) = parseReserve response-                 (jobContent, bytesRead) <- recvLen s (bytes)-                 recv s 2 -- Ending CRLF-                 return (Job (read jobid) jobContent)+                 jobContent <- recvBytes s bytes+                 recvBytes s 2 -- Ending CRLF+                 return (Job jobid jobContent)  -- | Delete a job to indicate that it has been completed.  If the job --   does not exist, was not reserved by this client, or is not in the@@ -243,7 +253,9 @@           -> IO () deleteJob bs jobid = withMVar bs task     where task s =-              do send s ("delete "++(show jobid)++"\r\n")+              do sendAll s $ toByteString (fromByteString "delete " `mappend`+                                           fromShow jobid `mappend`+                                           fromByteString "\r\n")                  response <- readLine s                  checkForBeanstalkErrors response @@ -255,10 +267,10 @@            -> IO () releaseJob bs jobid priority delay = withMVar bs task     where task s =-              do send s ("release " ++-                         (show jobid) ++ " " ++-                         (show priority) ++ " " ++-                         (show delay) ++ "\r\n")+              do sendAll s $ toByteString (fromByteString "release " `mappend`+                                           fromShow jobid `mappend` fromChar ' ' `mappend`+                                           fromShow priority `mappend` fromChar ' ' `mappend`+                                           fromShow delay `mappend` fromByteString "\r\n")                  response <- readLine s                  checkForBeanstalkErrors response @@ -269,11 +281,13 @@         -> IO () buryJob bs jobid pri = withMVar bs task     where task s =-              do send s ("bury "++(show jobid)++" "++(show pri)++"\r\n")+              do sendAll s $ toByteString (fromByteString "bury " `mappend`+                                           fromShow jobid `mappend` fromChar ' ' `mappend`+                                           fromShow pri `mappend` fromByteString "\r\n")                  response <- readLine s                  checkForBeanstalkErrors response -checkForBeanstalkErrors :: String -- ^ beanstalkd server response+checkForBeanstalkErrors :: B.ByteString -- ^ beanstalkd server response                         -> IO () checkForBeanstalkErrors input =     do eop OutOfMemoryException "OUT_OF_MEMORY\r\n"@@ -287,34 +301,32 @@        eop DeadlineSoonException "DEADLINE_SOON\r\n"        eop TimedOutException "TIMED_OUT\r\n"        eop NotIgnoredException "NOT_IGNORED\r\n"-       where eop e s = exceptionOnParse e (parse (string s) "errorParser" input)---- | When an error is successfully parsed, throw the given exception.-exceptionOnParse :: BeanstalkException -> Either a b -> IO ()-exceptionOnParse e x = case x of-                    Right _ -> E.throwIO e-                    Left _ -> return ()+       where eop e s = if B.take (B.length s) input == s then E.throwIO e else return ()  -- | Assign a tube for new jobs created with put command.  If the tube --   does not already exist, it will be created.  Initially, all --   sessions will use the tube named \"default\". useTube :: BeanstalkServer -- ^ Beanstalk server-        -> String -- ^ Name of tube to watch+        -> B.ByteString -- ^ Name of tube to watch         -> IO () useTube bs name = withMVar bs task     where task s =-              do send s ("use "++name++"\r\n");+              do sendAll s $ toByteString (fromByteString "use " `mappend`+                                           fromByteString name `mappend`+                                           fromByteString "\r\n")                  response <- readLine s                  checkForBeanstalkErrors response  -- | Add a named tube to the watch list, those tubes which --   'reserveJob' will request jobs from. watchTube :: BeanstalkServer -- ^ Beanstalk server-          -> String -- ^ Name of tube to watch+          -> B.ByteString -- ^ Name of tube to watch           -> IO Int -- ^ Number of tubes currently being watched watchTube bs name = withMVar bs task     where task s =-              do send s ("watch "++name++"\r\n");+              do sendAll s $ toByteString (fromByteString "watch " `mappend`+                                           fromByteString name `mappend`+                                           fromByteString "\r\n")                  response <- readLine s                  checkForBeanstalkErrors response                  return $ parseWatching response@@ -323,11 +335,13 @@ --   is the only one currently being watched, a 'NotIgnoredException' --   is thrown. ignoreTube :: BeanstalkServer -- ^ Beanstalk server-           -> String -- ^ Name of tube to ignore+           -> B.ByteString -- ^ Name of tube to ignore            -> IO Int -- ^ Number of tubes currently being watched ignoreTube bs name = withMVar bs task     where task s =-              do send s ("ignore "++name++"\r\n");+              do sendAll s $ toByteString (fromByteString "ignore " `mappend`+                                           fromByteString name `mappend`+                                           fromByteString "\r\n")                  response <- readLine s                  checkForBeanstalkErrors response                  return $ parseWatching response@@ -336,35 +350,35 @@ peekJob :: BeanstalkServer -- ^ Beanstalk server         -> Int -- ^ ID of job to get information about         -> IO Job -- ^ Job definition-peekJob bs jobid = genericPeek bs ("peek "++(show jobid))+peekJob bs jobid = genericPeek bs (fromByteString "peek " `mappend` fromShow jobid)  -- | Inspect the next ready job on the currently used tube. peekReadyJob :: BeanstalkServer -- ^ Beanstalk server              -> IO Job -- ^ Job definition-peekReadyJob bs = genericPeek bs "peek-ready"+peekReadyJob bs = genericPeek bs (fromByteString "peek-ready")  -- | Inspect the delayed job with shortest delay remaining on the currently used tube. peekDelayedJob :: BeanstalkServer -- ^ Beanstalk server                -> IO Job -- ^ Job definition-peekDelayedJob bs = genericPeek bs "peek-delayed"+peekDelayedJob bs = genericPeek bs (fromByteString "peek-delayed")  -- | Inspect the next buried job on the currently used tube. peekBuriedJob :: BeanstalkServer -- ^ Beanstalk server               -> IO Job -- ^ Job definition-peekBuriedJob bs = genericPeek bs "peek-buried"+peekBuriedJob bs = genericPeek bs (fromByteString "peek-buried")  -- Essence of the peek command.  Variations (peek, peek-ready, -- peek-delayed, peek-buried) just provide the command string, while -- this function actually executes it and parses the results.-genericPeek :: BeanstalkServer -> String -> IO Job+genericPeek :: BeanstalkServer -> Builder -> IO Job genericPeek bs cmd = withMVar bs task     where task s =-              do send s (cmd++"\r\n")+              do sendAll s $ toByteString (cmd `mappend` fromByteString "\r\n")                  response <- readLine s                  checkForBeanstalkErrors response                  let (jobid, bytes) = parseFoundIdLen response-                 (content,bytesRead) <- recvLen s (bytes)-                 recv s 2 -- Ending CRLF+                 content <- recvBytes s bytes+                 recvBytes s 2 -- Ending CRLF                  return (Job jobid content)  -- | Update the Time-To-Run (TTR) value for a job, giving a worker more time before job expiry.@@ -373,7 +387,9 @@          -> IO () touchJob bs jobid = withMVar bs task     where task s =-              do send s ("touch "++(show jobid)++"\r\n")+              do sendAll s $ toByteString (fromByteString "touch " `mappend`+                                           fromShow jobid `mappend`+                                           fromByteString "\r\n")                  response <- readLine s                  checkForBeanstalkErrors response                  return ()@@ -386,31 +402,30 @@          -> IO Int -- ^ Number of jobs actually kicked kickJobs bs maxcount = withMVar bs task     where task s =-              do send s ("kick "++(show maxcount)++"\r\n")+              do sendAll s $ toByteString (fromByteString "kick " `mappend`+                                           fromShow maxcount `mappend`+                                           fromByteString "\r\n")                  response <- readLine s                  checkForBeanstalkErrors response                  return (parseKicked response) -parseKicked :: String -> Int+parseKicked :: B.ByteString -> Int parseKicked input = case kparse of-                      Right a -> read a-                      Left _ -> 0 -- Error-    where kparse = parse (string "KICKED " >> many1 digit) "KickedParser" input+                      Done _ x -> x+                      _        -> 0 -- Error+    where kparse = parse (P.string "KICKED " *> P8.decimal) input  -- Essence of the various stats commands.  Variations provide the -- command, while this function actually executes it and parses the -- results.-genericStats :: BeanstalkServer -> String -> IO (M.Map String String)+genericStats :: BeanstalkServer -> Builder -> IO (M.Map B.ByteString B.ByteString) genericStats bs cmd = withMVar bs task     where task s =-              do send s (cmd++"\r\n")+              do sendAll s $ toByteString (cmd `mappend` fromByteString "\r\n")                  statHeader <- readLine s                  checkForBeanstalkErrors statHeader                  let bytes = parseOkLen statHeader-                 (statContent, bytesRead) <- recvLen s (bytes)-                 recv s 2 -- Ending CRLF-                 yamlN <- parseYaml statContent-                 return $ yamlMapToHMap yamlN+                 readYamlDict s  -- | Return statistical information about a job.  Keys that can be --   expected to be returned are the following:@@ -441,8 +456,8 @@ --   See the Beanstalk protocol docs for the definitive list and definitions. statsJob :: BeanstalkServer -- ^ Beanstalk server          -> Int -- ^ ID of job-         -> IO (M.Map String String) -- ^ Key-value map of job statistics-statsJob bs jobid = genericStats bs ("stats-job "++(show jobid))+         -> IO (M.Map B.ByteString B.ByteString) -- ^ Key-value map of job statistics+statsJob bs jobid = genericStats bs (fromByteString "stats-job " `mappend` fromShow jobid)  -- | Return statistical information about a tube.  Keys that can be --   expected to be returned are the following:@@ -480,22 +495,23 @@ -- --   See the Beanstalk protocol docs for the definitive list and definitions. statsTube :: BeanstalkServer -- ^ Beanstalk server-          -> String -- ^ Name of tube-          -> IO (M.Map String String) -- ^ Key-value map of tube statistics-statsTube bs tube = genericStats bs ("stats-tube "++tube)+          -> B.ByteString -- ^ Name of tube+          -> IO (M.Map B.ByteString B.ByteString) -- ^ Key-value map of tube statistics+statsTube bs tube =+    genericStats bs (fromByteString "stats-tube " `mappend` fromByteString tube)  -- | Print stats to screen in a readable format.-printStats :: M.Map String String -- ^ Key-value map of statistic names and values+printStats :: M.Map B.ByteString B.ByteString -- ^ Key-value map of statistic names and values            -> IO () -- ^ Screen output showing all \"key => value\" pairs printStats stats =     do let kv = M.assocs stats-       mapM_ (\(k,v) -> putStrLn (k ++ " => " ++ v)) kv+       mapM_ (\(k,v) -> B.putStr k >> B.putStr " => " >> B.putStrLn v) kv  -- | Pretty print a list.-printList :: [String] -- ^ List of names+printList :: [B.ByteString] -- ^ List of names           -> IO () -- ^ Screen output showing results with prefixed counter printList list =-    do mapM_ (\(n,x) -> putStrLn (" "++(show n)++". "++x)) (zip [1..] (list))+    do mapM_ (\(n,x) -> putStr (" "++(show n)++". ") >> B.putStrLn x) (zip [(1::Int)..] (list))  -- | Return statistical information about the server, across all --   clients.  Keys that can be expected to be returned are the@@ -604,70 +620,66 @@ -- --   See the Beanstalk protocol docs for the definitive list and definitions. statsServer :: BeanstalkServer -- ^ Beanstalk server-            -> IO (M.Map String String) -- ^ Key-value map of server statistics-statsServer bs = genericStats bs "stats"+            -> IO (M.Map B.ByteString B.ByteString) -- ^ Key-value map of server statistics+statsServer bs = genericStats bs (fromByteString "stats")  -- | Pause a tube for a specified time, so that reservations are no longer accepted. pauseTube :: BeanstalkServer -- ^ Beanstalk server-          -> String -- ^ Name of tube to pause+          -> B.ByteString -- ^ Name of tube to pause           -> Int -- ^ Number of seconds before reservations are accepted again           -> IO () pauseTube bs tube delay = withMVar bs task     where task s =-              do send s ("pause-tube "++tube++" "++(show delay)++"\r\n")+              do sendAll s $ toByteString (fromByteString "pause-tube " `mappend`+                                           fromByteString tube `mappend`+                                           fromChar ' ' `mappend`+                                           fromShow delay `mappend` fromByteString "\r\n")                  response <- readLine s                  checkForBeanstalkErrors response                  return ()  -- | List all existing tubes. listTubes :: BeanstalkServer -- ^ Beanstalk server-          -> IO [String] -- ^ Names of all tubes on the server+          -> IO [B.ByteString] -- ^ Names of all tubes on the server listTubes bs = genericList bs "list-tubes"  -- | List all watched tubes. listTubesWatched :: BeanstalkServer -- ^ Beanstalk server-                 -> IO [String] -- ^ Names of all currently watched tubes+                 -> IO [B.ByteString] -- ^ Names of all currently watched tubes listTubesWatched bs = genericList bs "list-tubes-watched"  -- | List used tube. listTubeUsed :: BeanstalkServer -- ^ Beanstalk server-             -> IO String -- ^ Name of current used tube+             -> IO B.ByteString -- ^ Name of current used tube listTubeUsed bs = withMVar bs task     where task s =-              do send s ("list-tube-used\r\n")+              do sendAll s ("list-tube-used\r\n")                  response <- readLine s                  checkForBeanstalkErrors response                  let tubeName = parseUsedTube response                  return tubeName -parseUsedTube :: String -> String+parseUsedTube :: B.ByteString -> B.ByteString parseUsedTube input =-    case (parse usedTubeParser "UsedTubeParser" input) of-      Right x -> x-      Left _ -> ""+    case (parse usedTubeParser input) of+      Done _ x -> x+      _        -> "" -nameParser = do initial <- leadingNameParser-                rest <- many1 (leadingNameParser <|> char '-')-                return (initial : rest)-                    where leadingNameParser = alphaNum <|> oneOf "+/;.$_()"+nameParser = B.cons <$> leadingNameParser <*> followingNameParser+    where leadingNameParser = P8.satisfy $ P8.inClass "a-zA-Z0-9+/;.$_()"+          followingNameParser = P.takeWhile $ inClass "-a-zA-Z0-9+/;.$_()" -usedTubeParser = do string "USING "-                    tube <- nameParser-                    string "\r\n"-                    return tube+usedTubeParser = P.string "USING " *> nameParser <* P.string "\r\n"  -- Essence of list commands that return YAML lists.-genericList :: BeanstalkServer -> String -> IO [String]+genericList :: BeanstalkServer -> B.ByteString -> IO [B.ByteString] genericList bs cmd = withMVar bs task     where task s =-              do send s (cmd++"\r\n")+              do sendAll s (cmd `B.append` "\r\n")                  lHeader <- readLine s                  checkForBeanstalkErrors lHeader                  let bytes = parseOkLen lHeader-                 (content, bytesRead) <- recvLen s (bytes)-                 recv s 2 -- Ending CRLF-                 yamlN <- parseYaml content-                 return $ yamlListToHList yamlN+                 readYamlList s  -- | Count number of jobs in a tube with a state in a given list. --   This is not part of the beanstalk protocol spec, so multiple@@ -675,116 +687,123 @@ --   may not be consistent (it does not represent one snapshot in --   time). jobCountWithState :: BeanstalkServer -- ^ Beanstalk server-                   -> String -- ^ Name of tube to inspect+                   -> B.ByteString -- ^ Name of tube to inspect                    -> [JobState] -- ^ List of valid states for count                    -> IO Int -- ^ Number of jobs with a state in the valid list jobCountWithState bs tube validStatuses =     do ts <- statsTube bs tube        let readyCount = case (elem READY validStatuses) of-                          True -> read (fromJust (M.lookup "current-jobs-ready" ts))+                          True -> parseIntBS (fromJust (M.lookup "current-jobs-ready" ts))                           False -> 0        let reservedCount = case (elem RESERVED validStatuses) of-                          True -> read (fromJust (M.lookup "current-jobs-reserved" ts))+                          True -> parseIntBS (fromJust (M.lookup "current-jobs-reserved" ts))                           False -> 0        let delayedCount = case (elem DELAYED validStatuses) of-                          True -> read (fromJust (M.lookup "current-jobs-delayed" ts))+                          True -> parseIntBS (fromJust (M.lookup "current-jobs-delayed" ts))                           False -> 0        let buriedCount = case (elem BURIED validStatuses) of-                          True -> read (fromJust (M.lookup "current-jobs-buried" ts))+                          True -> parseIntBS (fromJust (M.lookup "current-jobs-buried" ts))                           False -> 0        return (readyCount+reservedCount+delayedCount+buriedCount) -yamlListToHList :: YamlNode -> [String]-yamlListToHList y = elems where-    elist = (n_elem y)-    ESeq list = elist-    yelems = map n_elem list-    elems = map (\(EStr x) -> unpackBuf x) yelems--yamlMapToHMap :: YamlNode -> M.Map String String-yamlMapToHMap y = M.fromList elems where-    emap = (n_elem y)-    EMap maplist = emap -- [(YamlNode,YamlNode)]-    yelems = map (\(x,y) -> (n_elem x, n_elem y))  maplist-    elems = map (\(EStr x, EStr y) -> (unpackBuf x, unpackBuf y)) yelems---- Read a single character from socket without handling errors.-readChar :: Socket -> IO Char-readChar s = recv s 1 >>= return . head- -- Read up to and including a newline.  Any errors result in a string -- starting with "Error: "-readLine :: Socket -> IO String+readLine :: Socket -> IO B.ByteString readLine s =-    catch readLine' (\err -> return ("Error: " ++ show err))+    catch readLine' (\err -> return (B.pack $ "Error: " ++ show err))         where-          readLine' = do c <- readChar s-                         if c == '\n'-                           then return (c:[])-                           else do l <- readLine s-                                   return (c:l)+          readLine' = readline'' (fromByteString B.empty) >>= return . toByteString+              where readline'' b = do c <- recvBytes s 1+                                      if B.head c == '\n'+                                        then return     (b `mappend` fromByteString c)+                                        else readline'' (b `mappend` fromByteString c)  -- Parse response from watch/ignore command to determine how many -- tubes are currently being watched.-parseWatching :: String -> Int+-- Parse response from watch/ignore command to determine how many+-- tubes are currently being watched.+parseWatching :: B.ByteString -> Int parseWatching input =-    case (parse (string "WATCHING " >> many1 digit) "WatchParser" input) of-      Right x -> read x-      Left _ -> 0+    case (parse (P.string "WATCHING " *> P8.decimal) input) of+      Done _ x -> x+      _        -> 0  -- Parse response from put command.-parsePut :: String -> (JobState, Int)+parsePut :: B.ByteString -> (JobState, Int) parsePut input =-    case (parse putParser "PutParser" input) of-      Right x -> x-      Left _ -> (READY, 0) -- Error+    case (parse putParser input) of+      Done _ x -> x+      _        -> (READY, 0)  -- Error -putParser = do stateStr <- many1 letter-               char ' '-               jobid <- many1 digit-               let state = case stateStr of-                             "BURIED" -> BURIED-                             _ -> READY-               return (state, read jobid)+putParser = (,) <$> (parseStateStr <$> takeTill (==32) <* P8.space) <*> P8.decimal+    where parseStateStr "BURIED" = BURIED+          parseStateStr _        = READY  -- Get Job ID and size.-parseReserve :: String -> (String,Int)+parseReserve :: B.ByteString -> (Int,Int) parseReserve input =-    case (parse reservedParser "ReservedParser" input) of-      Right (x,y) -> (x, read y)-      Left _ -> ("",0)+    case (parse reservedParser input) of+      Done _ x -> x+      _        -> (-1, 0)  -- Error --- Parse response from reserve command, including job id and bytes of body--- to come next.-reservedParser :: GenParser Char st (String,String)-reservedParser = do string "RESERVED"-                    char ' '-                    x <- many1 digit-                    char ' '-                    y <- many1 digit-                    return (x,y)+reservedParser = P.string "RESERVED " *> ((,) <$> P8.decimal <* P8.space <*> P8.decimal)  -- Get number of bytes from an OK <bytes> response string.-parseOkLen :: String -> Int+parseOkLen :: B.ByteString -> Int parseOkLen input =-        case (parse okLenParser "okLenParser" input) of-          Right len -> read len-          Left err -> 0---- Parser for first line of stats for data length indicator.-okLenParser :: GenParser Char st String-okLenParser = string "OK " >> many1 digit+    case (parse (P.string "OK " *> P8.decimal) input) of+      Done _ x -> x+      _        -> 0  -- Get job id and number of bytes from FOUND response string.-parseFoundIdLen :: String -> (Int,Int)+parseFoundIdLen :: B.ByteString -> (Int,Int) parseFoundIdLen input =-    case (parse foundIdLenParser "FoundIdLenParser" input) of-      Right x -> x-      Left _ -> (0,0)+    case (parse foundIdLenParser input) of+      Done _ x -> x+      _        -> (0,0) -foundIdLenParser :: GenParser Char st (Int,Int)-foundIdLenParser = do string "FOUND "-                      jobid <- many1 digit-                      string " "-                      bytes <- many1 digit-                      return (read jobid, read bytes)+foundIdLenParser = P.string "FOUND " *> ((,) <$> P8.decimal <* P8.space <*> P8.decimal)++-- Read a YAML list, and parse it.+readYamlList s = do result <- parseWith (recv s 1024) yamlListParser ""+                    case result of+                      Done _ x -> return x+                      _        -> return []++yamlListParser = start_line *> many list_item <* P.string "\r\n"+    where start_line = P.string "---" *> P8.endOfLine+          list_item = P.string "- " *> takeTill P8.isEndOfLine <* P8.endOfLine++-- Read a YAML dict, parse it, and return a Map+readYamlDict s = do result <- parseWith (recv s 1024) yamlDictParser ""+                    case result of+                      Done _ x -> return x+                      _        -> return M.empty++yamlDictParser = start_line *> (M.fromList <$> many dict_item) <* P.string "\r\n"+    where start_line = P.string "---" *> P8.endOfLine+          dict_item = (,) <$> dict_key <*> dict_value+          dict_key = takeTill colon_or_newline <* P.string ": "+          colon_or_newline byte = byte == 58 || P8.isEndOfLine byte+          dict_value = takeTill P8.isEndOfLine <* P8.endOfLine++-- Parse an integer ByteString+parseIntBS :: B.ByteString -> Int+parseIntBS input =+    case feed (parse P8.decimal input) "" of+      Done _ x -> x+      _        -> error "Beanstalk.parseIntBS: no parse"++-- Read bytes from a socket, and return the bytestring. Blocks until the given+-- number of bytes has been read.+recvBytes :: Socket -> Int -> IO B.ByteString+recvBytes s bytes = recv' (fromByteString "") bytes+    where recv' b 0 = return $ toByteString b+          recv' b n = do+            chunk <- recv s (min n 1024)+            let n' = n - B.length chunk+            if n' == n+              then ioError $ userError $ "Could not read " ++ (show bytes) +++                       " bytes from beanstalkd; server disconnect."+              else+                  recv' (b `mappend` fromByteString chunk) n'
Tests.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE OverloadedStrings #-} ----------------------------------------------------------------------------- -- | -- Module      :  Beanstalk Tests@@ -25,6 +26,7 @@ import qualified Control.Exception as E import Control.Monad import Control.Concurrent(threadDelay)+import qualified Data.ByteString.Char8 as B  bs_host = "localhost" bs_port = "11300"@@ -137,7 +139,7 @@     TestCase (               do (bs, tt) <- connectAndSelectRandomTube                  randString <- randomName-                 let body = "My test job body, " ++ randString+                 let body = "My test job body, " `B.append` randString                  (_,put_job_id) <- putJob bs 1 0 60 body                  rsv_job <- reserveJob bs                  assertEqual "Reserved job ID should match what was put" put_job_id (job_id rsv_job)@@ -151,7 +153,7 @@     TestCase (               do (bs, tt) <- connectAndSelectRandomTube                  randString <- randomName-                 let body = "My test job body, " ++ randString+                 let body = "My test job body, " `B.append` randString                  (_,put_job_id) <- putJob bs 1 0 60 body                  rsv_job <- reserveJobWithTimeout bs 2                  assertEqual "Reserved job should match job that was just put"@@ -163,9 +165,9 @@     TestCase (               do (bs, tt) <- connectAndSelectRandomTube                  randString <- randomName-                 let body = "My test job body, " ++ randString+                 let body = "My test job body, " `B.append` randString                  (_,put_job_id) <- putJob bs 1 0 60 body-                 let next_body = "My test job body, " ++ randString+                 let next_body = "My test job body, " `B.append` randString                  (_,put_next_job_id) <- putJob bs 1 0 60 next_body                  peeked_job <- peekJob bs put_job_id                  assertEqual "Peeked job id should match job id that was just put"@@ -183,7 +185,7 @@     TestCase (               do (bs, tt) <- connectAndSelectRandomTube                  randString <- randomName-                 let body = "My test job body, " ++ randString+                 let body = "My test job body, " `B.append` randString                  (_,put_job_id) <- putJob bs 1 5 60 body                  kicked <- kickJobs bs 1                  assertEqual "Kick should indicate one job kicked" 1 kicked@@ -196,7 +198,7 @@                  assertJobsCount bs tt [READY] 0 "New tube has no jobs"                  -- Put a job on the tube                  randString <- randomName-                 let body = "My test job body, " ++ randString+                 let body = "My test job body, " `B.append` randString                  (_,put_job_id) <- putJob bs 1 0 60 body                  assertJobsCount bs tt [READY] 1 "Put adds job to tube"                  -- Reserve the job@@ -252,7 +254,7 @@               do (bs, tt) <- connectAndSelectRandomTube                  assertJobsCount bs tt [READY] 0 "New tube has no jobs"                  randString <- randomName-                 let jobcontent = "new job "++randString+                 let jobcontent = "new job " `B.append` randString                  (_,put_job_id) <- putJob bs 1 0 60 jobcontent                  assertJobsCount bs tt [READY] 1 "Put creates new ready job"                  job <- peekJob bs put_job_id@@ -296,8 +298,8 @@                  let priority = 99                  (job_state ,put_job_id) <- putJob bs priority 0 60 "new job"                  job_stats <- statsJob bs put_job_id-                 assertEqual "Job ID matches" put_job_id (read (fromJust (M.lookup "id" job_stats)))-                 assertEqual "Job priority matches" priority (read (fromJust (M.lookup "pri" job_stats)))+                 assertEqual "Job ID matches" put_job_id (read $ B.unpack (fromJust (M.lookup "id" job_stats)))+                 assertEqual "Job priority matches" priority (read $ B.unpack (fromJust (M.lookup "pri" job_stats)))              )  -- Test finding server statistics.@@ -305,7 +307,7 @@     TestCase (               do (bs, tt) <- connectAndSelectRandomTube                  stats <- statsServer bs-                 assertBool "More than 1 job has been created" (1 < (read (fromJust (M.lookup "total-jobs" stats))))+                 assertBool "More than 1 job has been created" (1 < (read $ B.unpack (fromJust (M.lookup "total-jobs" stats))))              )  -- Test listing all tubes for the server.@@ -347,8 +349,8 @@                  jobstat_before <- statsJob bs jobid                  touchJob bs jobid                  jobstat_after <- statsJob bs jobid-                 let ttr_before = ((read (fromJust (M.lookup "time-left" jobstat_before)))::Int)-                 let ttr_after = ((read (fromJust (M.lookup "time-left" jobstat_after)))::Int)+                 let ttr_before = ((read $ B.unpack (fromJust (M.lookup "time-left" jobstat_before)))::Int)+                 let ttr_after = ((read $ B.unpack (fromJust (M.lookup "time-left" jobstat_after)))::Int)                  assertBool "TTR extended by touch" (ttr_after >= ttr_before)              ) @@ -359,7 +361,7 @@                  tubestat_before <- statsTube bs tt                  pauseTube bs tt 1000                  tubestat_after <- statsTube bs tt-                 let paused_rem = ((read (fromJust (M.lookup "pause-time-left" tubestat_after)))::Int)+                 let paused_rem = ((read $ B.unpack (fromJust (M.lookup "pause-time-left" tubestat_after)))::Int)                  -- Check that at least 990 seconds still remains of the                  -- original 1000 seconds we paused the tube for.                  assertBool "Tube has at least 990 seconds before un-pausing" (paused_rem > 990)@@ -380,7 +382,7 @@     TestCase (               do (bs, tt) <- connectAndSelectRandomTube                  rname <- randomName-                 e <- E.tryJust (guard . isBadFormatException) (statsTube bs ("-"++rname))+                 e <- E.tryJust (guard . isBadFormatException) (statsTube bs ("-" `B.append` rname))                  case e of                    Right _ -> assertFailure "Using tube name starting with hyphen should fail"                    Left _ -> return ()@@ -448,7 +450,7 @@  -- Assert a number of jobs on a given tube with one of the states -- listed.-assertJobsCount :: BeanstalkServer -> String -> [JobState] -> Int -> String -> IO ()+assertJobsCount :: BeanstalkServer -> B.ByteString -> [JobState] -> Int -> String -> IO () assertJobsCount bs tube states jobs msg =     do ts <- statsTube bs tube        jobsReady <- jobCountWithState bs tube states@@ -457,7 +459,7 @@  -- Configure a new beanstalkd connection to use&watch a single tube -- with a random name.-connectAndSelectRandomTube :: IO (BeanstalkServer, String)+connectAndSelectRandomTube :: IO (BeanstalkServer, B.ByteString) connectAndSelectRandomTube =     do bs <- connectBeanstalk bs_host bs_port        tt <- randomName@@ -467,7 +469,7 @@        return (bs, tt)  -- Generate random tube names for test separation.-randomName :: IO String+randomName :: IO B.ByteString randomName =     do rdata <- randomIO :: IO Integer-       return (show (abs rdata))+       return $ B.pack (show (abs rdata))
hbeanstalk.cabal view
@@ -1,5 +1,5 @@ Name:           hbeanstalk-Version:        0.1+Version:        0.2 License:        BSD3 License-file:   LICENSE Cabal-Version: >= 1.6@@ -24,7 +24,9 @@  Library -  Build-depends:  base >= 4 && < 5, network, containers >= 0.3.0.0, HsSyck >= 0.45, parsec >= 2.1.0.1+  Build-depends:  base >= 4 && < 5, network, containers >= 0.3.0.0,+                  blaze-builder >= 0.2.1.0, network-bytestring >= 0.1.2.0,+                  bytestring >= 0.9.1.7, attoparsec >= 0.8.3.0    Exposed-modules:         Network.Beanstalk