{-# LANGUAGE ScopedTypeVariables, TemplateHaskell, TypeFamilies #-} module Imm.Core ( -- * Types FeedConfig, FeedList, -- * Actions importOPML, check, showStatus, markAsRead, markAsUnread, update, ) where -- {{{ Imports import Imm.Config import Imm.Database import Imm.Error import Imm.Feed (FeedParser(..)) import qualified Imm.Feed as Feed import qualified Imm.HTTP as HTTP import qualified Imm.Maildir as Maildir import Imm.Mail (MailFormatter(..)) import qualified Imm.Mail as Mail import Imm.OPML as OPML import Imm.Util import Control.Concurrent.Async import Control.Monad hiding(forM_, mapM_) import Control.Monad.Error hiding(forM_, mapM_) -- import Control.Monad.Reader hiding(forM_, mapM_) import Control.Monad.Trans.Control import Data.Foldable hiding(foldr) import Data.Time as T import Prelude hiding(log, mapM_, sum) import System.Log.Logger import Text.Feed.Query as F import Text.Feed.Types as F -- }}} -- {{{ Types type FeedConfig = (Config -> Config, FeedID) type FeedList = [FeedConfig] -- }}} importOPML :: (MonadBase IO m, MonadPlus m) => String -> m () importOPML = mapM_ addFeeds . OPML.read check :: (MonadBaseControl IO m, FeedParser m, ConfigReader m, DatabaseReader m, HTTP.Decoder m, MonadError ImmError m) => FeedList -> m () check feeds = void . liftBaseWith $ \runInIO -> mapConcurrently (runInIO . checkFeed) feeds checkFeed :: (MonadBase IO m, FeedParser m, ConfigReader m, DatabaseReader m, HTTP.Decoder m, MonadError ImmError m) => FeedConfig -> m () checkFeed (f, feedID) = localConfig f . localError "imm.core" $ Feed.download feedID >>= Feed.check showStatus :: (MonadBase IO m, ConfigReader m, DatabaseReader m, MonadError ImmError m) => FeedConfig -> m () showStatus (f, feedID) = localConfig f . localError "imm.core" $ (io . noticeM "imm.core" =<< Feed.showStatus feedID) markAsRead :: (MonadBase IO m, ConfigReader m, DatabaseState m, MonadError ImmError m) => FeedConfig -> m () markAsRead (f, feedID) = localConfig f . localError "imm.core" $ Feed.markAsRead feedID markAsUnread :: (MonadBase IO m, ConfigReader m, DatabaseState m, MonadError ImmError m) => FeedConfig -> m () markAsUnread (f, feedID) = localConfig f . localError "imm.core" $ Feed.markAsUnread feedID update :: (MonadBaseControl IO m, ConfigReader m, DatabaseState m, MonadError ImmError m, FeedParser m, MailFormatter m, HTTP.Decoder m) => FeedList -> m () update feeds = void . liftBaseWith $ \runInIO -> mapConcurrently (runInIO . updateFeed) feeds -- | Write mails for each new item, and update the last check time in state file. updateFeed :: (Applicative m, ConfigReader m, DatabaseState m, FeedParser m, MailFormatter m, MonadBase IO m, HTTP.Decoder m, MonadError ImmError m) => FeedConfig -> m () updateFeed (f, feedID) = localConfig f . localError "imm.core" $ do -- io . noticeM "imm.core" $ "Updating: " ++ show feedID (uri, feed) <- Feed.download feedID Maildir.create =<< readConfig maildir io . debugM "imm.core" $ Feed.describe feed lastCheck <- getLastCheck uri (results :: [Integer]) <- forM (feedItems feed) $ \item -> do date <- Feed.getDate item (date > lastCheck) ? (updateItem (item, feed) >> return 1) ?? return 0 io . noticeM "imm.core" $ "==> " ++ show (sum results) ++ " new item(s) for <" ++ show feedID ++ ">" Feed.markAsRead uri updateItem :: (Applicative m, ConfigReader m, FeedParser m, MailFormatter m, MonadBase IO m, MonadError ImmError m) => (Item, Feed) -> m () updateItem (item, feed) = do timeZone <- io getCurrentTimeZone dir <- readConfig maildir io . debugM "imm.core" $ "Adding following item to maildir [" ++ dir ++ "]:\n" ++ Feed.describeItem item Maildir.add dir =<< Mail.build timeZone (item, feed)