2 {-# LANGUAGE OverloadedStrings #-}
4 -- generic AMQP publisher
5 import Control.Concurrent
6 import qualified Control.Exception as X
7 import Control.Monad (forever)
8 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)
14 import qualified Data.Text as T
16 import Data.Time.Clock.POSIX
17 import Data.Version (showVersion)
18 import Data.Word (Word64)
21 import Network.AMQP.Types
22 import Network.AMQP.Utils.Connection
23 import Network.AMQP.Utils.Helpers
24 import Network.AMQP.Utils.Options
25 import Paths_amqp_utils (version)
26 import System.Directory
27 import System.Environment
30 import qualified System.Posix.Files as F
36 args <- getArgs >>= parseargs 'a'
37 printparam "client version" ["amqp-utils", showVersion version]
38 printparam "routing key" $ rKey args
39 printparam "exchange" $ currentExchange args
41 if inputFile args == "-"
43 else F.getFileStatus (inputFile args) >>= return . F.isDirectory
46 printparam "hotfolder" (inputFile args) >>
47 printparam "initial scan" (initialScan args)
55 printparam "remove sent file" (removeSentFile args && isDir)
56 (conn, chan) <- connect args
57 addChannelExceptionHandler chan (X.throwTo tid)
58 printparam "confirm mode" $ confirm args
61 confirmSelect chan False
62 addConfirmationListener chan confirmCallback
64 let publishOneMsg = publishOneMsg' chan args {removeSentFile = removeSentFile args && isDir}
68 setCurrentDirectory (inputFile args)
70 then getDirectoryContents "." >>= mapM_ (\fn -> handleFile publishOneMsg (suffix args) fn)
72 inotify <- initINotify
78 (handleEvent publishOneMsg (suffix args))
79 hr $ "BEGIN watching " ++ (inputFile args)
80 _ <- forever $ threadDelay 1000000
82 hr $ "END watching " ++ (inputFile args)
86 if inputFile args == "-"
88 else BL.readFile (inputFile args)
90 then mapM_ (publishOneMsg Nothing) (BL.lines messageFile)
91 else publishOneMsg (Just (inputFile args)) messageFile
94 -- all done. wait and close.
96 then waitForConfirms chan >>= printparam "confirmed"
98 X.catch (closeConnection conn) exceptionHandler
100 -- | A handler for clean exit
101 exceptionHandler :: AMQPException -> IO ()
102 exceptionHandler (ChannelClosedException Normal txt) =
103 printparam "exit" txt >> exitWith ExitSuccess
104 exceptionHandler (ConnectionClosedException Normal txt) =
105 printparam "exit" txt >> exitWith ExitSuccess
106 exceptionHandler x = printparam "exception" x >> exitWith (ExitFailure 1)
108 -- | The handler for publisher confirms
109 confirmCallback :: (Word64, Bool, AckType) -> IO ()
110 confirmCallback (deliveryTag, isAll, ackType) =
120 -- | Hotfolder event handler
122 (Maybe String -> BL.ByteString -> IO ())
126 -- just handle closewrite and movedin events
127 #if MIN_VERSION_hinotify(0,3,10)
128 handleEvent func suffixes (Closed False (Just fileName) True) =
129 handleFile func suffixes (BS.unpack fileName)
130 handleEvent func suffixes (MovedIn False fileName _) =
131 handleFile func suffixes (BS.unpack fileName)
133 handleEvent func suffixes (Closed False (Just fileName) True) = handleFile func suffixes fileName
134 handleEvent func suffixes (MovedIn False fileName _) = handleFile func suffixes fileName
136 handleEvent _ _ _ = return ()
138 -- | Hotfolder file handler
140 (Maybe String -> BL.ByteString -> IO ()) -> [String] -> FilePath -> IO ()
141 handleFile _ _ ('.':_) = return () -- ignore hidden files
142 handleFile func suffixes@(_:_) fileName =
143 if any (flip isSuffixOf fileName) suffixes
144 then handleFile func [] fileName
146 handleFile func [] fileName =
148 (BL.readFile fileName >>= func (Just fileName))
149 (\e -> printparam "exception in handleFile" (e :: X.SomeException))
151 -- | Publish one message with our settings
152 publishOneMsg' :: Channel -> Args -> Maybe FilePath -> BL.ByteString -> IO ()
153 publishOneMsg' chan a fn content = do
154 printparam "sending" fn
155 (mtype, mencoding) <-
156 if (magic a) && isJust fn
158 m <- magicOpen [MagicMimeType]
160 t <- magicFile m (fromJust fn)
161 magicSetFlags m [MagicMimeEncoding]
162 e <- magicFile m (fromJust fn)
163 return (Just (T.pack t), Just (T.pack e))
164 else return ((contenttype a), (contentencoding a))
165 now <- getCurrentTime >>= return . floor . utcTimeToPOSIXSeconds
168 (T.pack $ currentExchange a)
172 , msgDeliveryMode = persistent a
173 , msgTimestamp = Just now
175 , msgType = msgtype a
176 , msgUserID = userid a
177 , msgApplicationID = appid a
178 , msgClusterID = clusterid a
179 , msgContentType = mtype
180 , msgContentEncoding = mencoding
181 , msgReplyTo = replyto a
182 , msgPriority = prio a
183 , msgCorrelationID = corrid a
184 , msgExpiration = msgexp a
185 , msgHeaders = substheader (fnheader a) fn $ msgheader a
188 removeSentFileIfRequested (removeSentFile a) fn
191 [String] -> Maybe String -> Maybe FieldTable -> Maybe FieldTable
192 substheader (s:r) (Just fname) old =
193 substheader r (Just fname) (addheader old (s ++ "=" ++ fname))
194 substheader _ _ old = old
195 removeSentFileIfRequested False _ = return ()
196 removeSentFileIfRequested True Nothing = return ()
197 removeSentFileIfRequested True (Just fname) = printparam "removing" fname >> removeFile fname