-- The bang pattern at least on @buffer@ is necessary for
-- performance reasons.
--
+-- We specify a timeout of fifteen minutes on the 'recv_line'
+-- function, after which we will return to our caller. This should
+-- cause the connection to be dropped, and a new one initiated. The
+-- timeout is in response to observed behavior where the feed
+-- eventually stops transmitting data entirely without closing the
+-- connection.
+--
loop :: Configuration
-> Handle -- ^ Handle to the feed (network connection)
-> [String] -- ^ Current XML document buffer, line-by-line, in reverse
-> IO ()
loop !cfg !h !buffer = do
- line <- recv_line h
- let new_buffer = line : buffer
-
- -- Use isPrefixOf to avoid line-ending issues. Hopefully they won't
- -- send invalid junk (on the same line) after closing the root
- -- element.
- if "</message>" `isPrefixOf` line
- then do
- -- The buffer is in reverse (newest first) order, though, so we
- -- have to reverse it first. We then concatenate all of its lines
- -- into one big string.
- let document = concat $ reverse new_buffer
- save_document cfg document
- loop cfg h [] -- Empty the buffer before looping again.
- else
- -- Append line to the head of the buffer and loop.
- loop cfg h new_buffer
+ line' <- timeout fifteen_minutes $ recv_line h
+ case line' of
+ -- If we haven't received anything in fifteen minutes, return back
+ -- to the calling function. This should only happen in the case of
+ -- an error, and our caller should be prepared to handle it.
+ Nothing -> report_warning "No data received for 15 minutes."
+
+ Just line -> do
+ -- If the recv didn't timeout, proceed normally.
+ let new_buffer = line : buffer
+
+ -- Use isPrefixOf to avoid line-ending issues. Hopefully they won't
+ -- send invalid junk (on the same line) after closing the root
+ -- element.
+ if "</message>" `isPrefixOf` line
+ then do
+ -- The buffer is in reverse (newest first) order, though, so we
+ -- have to reverse it first. We then concatenate all of its lines
+ -- into one big string.
+ let document = concat $ reverse new_buffer
+ save_document cfg document
+ loop cfg h [] -- Empty the buffer before looping again.
+ else
+ -- Append line to the head of the buffer and loop.
+ loop cfg h new_buffer
+ where
+ fifteen_minutes :: Int
+ fifteen_minutes = 15 * 60 * 1000000
-- | Once we're connected to a feed, we need to log in. There's no
connect_and_parse :: Configuration
-> String -- ^ Hostname to connect to
-> IO ()
-connect_and_parse cfg host = do
- report_info $ "Connecting to " ++ host ++ "."
+connect_and_parse cfg host =
bracket acquire_handle release_handle action
- return ()
where
five_seconds :: Int
- five_seconds = 5000000
+ five_seconds = 5 * 1000000
+
+ acquire_handle = do
+ report_info $ "Connecting to " ++ host ++ "."
+ connectTo host (PortNumber 4500)
+
+ release_handle h = do
+ report_info $ "Closing connection to " ++ host ++ "."
+ hClose h
- acquire_handle = connectTo host (PortNumber 4500)
- release_handle = hClose
action h = do
-- No buffering anywhere.
hSetBuffering h NoBuffering
case login_worked of
Nothing -> report_info $ "Login timed out (5 seconds). "
++ "Waiting 5 seconds to reconnect."
+
+ -- If loop returns (due to its timeout), it will pop out right
+ -- here and the action will terminate causing 'release_handle'
+ -- to trigger.
Just _ -> loop cfg h []
let cfg = (def :: Configuration) `merge_optional` opt_config
init_logging (log_level cfg) (log_file cfg) (syslog cfg)
- -- Check the optional config for missing required options. This is
- -- necessary because if the user specifies an empty list of
- -- hostnames in e.g. the config file, we want to bail rather than
- -- fall back on the default list (which was merged from a default
- -- Configuration above).
- when (null $ get_feed_hosts (OC.feed_hosts opt_config)) $ do
- report_error "No feed hosts supplied."
- exitWith (ExitFailure exit_no_feed_hosts)
-
+ -- Check the optional config for missing required options.
when (isNothing (OC.password opt_config)) $ do
report_error "No password supplied."
exitWith (ExitFailure exit_no_password)
report_error "No username supplied."
exitWith (ExitFailure exit_no_username)
+ -- This should be impossible. We had a choice to make: since the
+ -- command-line feed_hosts are usually not supplied, we don't want
+ -- to take the empty list supplied on the command-line and use
+ -- that. But that means that we need to do the same thing if the
+ -- user supplies an empty list in the config file. That "same thing"
+ -- is to use the default list. So, this should never be empty,
+ -- because if the optional config has no feed hosts, we use the
+ -- default list.
+ when (null $ get_feed_hosts (feed_hosts cfg)) $ do
+ report_error "No feed hosts supplied."
+ exitWith (ExitFailure exit_no_feed_hosts)
+
when (daemonize cfg) $ do
-- Old PID files can be left around after an unclean shutdown. We
-- only care if we're running as a daemon.