2 {-# LANGUAGE OverloadedStrings #-}
4 -- generic AMQP publisher
5 import Control.Concurrent
6 import qualified Control.Exception as X
7 import qualified Data.ByteString.Lazy.Char8 as BL
9 #if MIN_VERSION_hinotify(0,3,10)
10 import qualified Data.ByteString.Char8 as BS
12 import Data.List (isSuffixOf)
15 import qualified Data.Text as T
17 import Data.Time.Clock.POSIX
18 import Data.Version (showVersion)
19 import Data.Word (Word64)
22 import Network.AMQP.Types
23 import Network.AMQP.Utils.Connection
24 import Network.AMQP.Utils.Helpers
25 import Network.AMQP.Utils.Options
26 import Paths_amqp_utils (version)
27 import System.Directory
28 import System.Environment
33 import qualified System.Posix.Files as F
39 args <- getArgs >>= parseargs 'a'
40 printparam "client version" ["amqp-utils", showVersion version]
41 printparam "routing key" $ rKey args
42 printparam "exchange" $ currentExchange args
44 if inputFile args == "-"
46 else F.getFileStatus (inputFile args) >>= return . F.isDirectory
48 then printparam "hotfolder" (inputFile args) >>
49 printparam "initial scan" (initialScan args)
57 printparam "remove sent file" (removeSentFile args && isDir)
58 (conn, chan) <- connect args
59 addChannelExceptionHandler chan (X.throwTo tid)
60 printparam "confirm mode" $ confirm args
63 confirmSelect chan False
64 addConfirmationListener chan confirmCallback
67 publishOneMsg' chan args {removeSentFile = removeSentFile args && isDir}
72 setCurrentDirectory (inputFile args)
74 then getDirectoryContents "." >>=
75 mapM_ (\fn -> handleFile publishOneMsg (suffix args) fn)
77 inotify <- initINotify
83 (handleEvent publishOneMsg (suffix args))
84 hr $ "BEGIN watching " ++ (inputFile args)
85 sleepingBeauty >>= printparam "exception"
87 hr $ "END watching " ++ (inputFile args)
89 X.throw (X.ErrorCall "ERROR: watching a directory is only supported in Linux")
94 if inputFile args == "-"
96 else BL.readFile (inputFile args)
98 then mapM_ (publishOneMsg Nothing) (BL.lines messageFile)
99 else publishOneMsg (Just (inputFile args)) messageFile
102 -- all done. wait and close.
104 then waitForConfirms chan >>= printparam "confirmed"
106 X.catch (closeConnection conn) exceptionHandler
108 -- | A handler for clean exit
109 exceptionHandler :: AMQPException -> IO ()
110 exceptionHandler (ChannelClosedException Normal txt) =
111 printparam "exit" txt >> exitWith ExitSuccess
112 exceptionHandler (ConnectionClosedException Normal txt) =
113 printparam "exit" txt >> exitWith ExitSuccess
114 exceptionHandler x = printparam "exception" x >> exitWith (ExitFailure 1)
116 -- | The handler for publisher confirms
117 confirmCallback :: (Word64, Bool, AckType) -> IO ()
118 confirmCallback (deliveryTag, isAll, ackType) =
129 -- | Hotfolder event handler
131 (Maybe String -> BL.ByteString -> IO ()) -> [String] -> Event -> IO ()
132 -- just handle closewrite and movedin events
133 #if MIN_VERSION_hinotify(0,3,10)
134 handleEvent func suffixes (Closed False (Just fileName) True) =
135 handleFile func suffixes (BS.unpack fileName)
136 handleEvent func suffixes (MovedIn False fileName _) =
137 handleFile func suffixes (BS.unpack fileName)
139 handleEvent func suffixes (Closed False (Just fileName) True) =
140 handleFile func suffixes fileName
141 handleEvent func suffixes (MovedIn False fileName _) =
142 handleFile func suffixes fileName
144 handleEvent _ _ _ = return ()
146 -- | Hotfolder file handler
148 (Maybe String -> BL.ByteString -> IO ()) -> [String] -> FilePath -> IO ()
149 handleFile _ _ ('.':_) = return () -- ignore hidden files
150 handleFile func suffixes@(_:_) fileName =
151 if any (flip isSuffixOf fileName) suffixes
152 then handleFile func [] fileName
154 handleFile func [] fileName =
156 (BL.readFile fileName >>= func (Just fileName))
157 (\e -> printparam "exception in handleFile" (e :: X.IOException))
160 -- | Publish one message with our settings
161 publishOneMsg' :: Channel -> Args -> Maybe FilePath -> BL.ByteString -> IO ()
162 publishOneMsg' chan a fn content = do
163 printparam "sending" fn
164 (mtype, mencoding) <-
165 if (magic a) && isJust fn
167 m <- magicOpen [MagicMimeType]
169 t <- magicFile m (fromJust fn)
170 magicSetFlags m [MagicMimeEncoding]
171 e <- magicFile m (fromJust fn)
172 return (Just (T.pack t), Just (T.pack e))
173 else return ((contenttype a), (contentencoding a))
174 now <- getCurrentTime >>= return . floor . utcTimeToPOSIXSeconds
177 (T.pack $ currentExchange a)
181 , msgDeliveryMode = persistent a
182 , msgTimestamp = Just now
184 , msgType = msgtype a
185 , msgUserID = userid a
186 , msgApplicationID = appid a
187 , msgClusterID = clusterid a
188 , msgContentType = mtype
189 , msgContentEncoding = mencoding
190 , msgReplyTo = replyto a
191 , msgPriority = prio a
192 , msgCorrelationID = corrid a
193 , msgExpiration = msgexp a
194 , msgHeaders = substheader (fnheader a) fn $ msgheader a
197 removeSentFileIfRequested (removeSentFile a) fn
200 [String] -> Maybe String -> Maybe FieldTable -> Maybe FieldTable
201 substheader (s:r) (Just fname) old =
202 substheader r (Just fname) (addheader old (s ++ "=" ++ fname))
203 substheader _ _ old = old
204 removeSentFileIfRequested False _ = return ()
205 removeSentFileIfRequested True Nothing = return ()
206 removeSentFileIfRequested True (Just fname) =
207 printparam "removing" fname >> removeFile fname