fb-1.0: src/Facebook/Pager.hs
{-# LANGUAGE ConstraintKinds, DeriveDataTypeable, FlexibleContexts, OverloadedStrings #-}
module Facebook.Pager
( Pager(..)
, fetchNextPage
, fetchPreviousPage
, fetchAllNextPages
, fetchAllPreviousPages
) where
import Control.Applicative
import Control.Monad (mzero)
import Control.Monad.IO.Class (liftIO)
import Control.Monad.Trans.Control (MonadBaseControl)
import Control.Monad.Trans.Resource (MonadResourceBase)
import Data.Typeable (Typeable)
import qualified Control.Monad.Trans.Resource as R
import qualified Data.Aeson as A
import qualified Data.Conduit as C
import qualified Network.HTTP.Conduit as H
import Facebook.Base
import Facebook.Monad
-- | Many Graph API results are returned as a JSON object with
-- the following structure:
--
-- @
-- {
-- \"data\": [
-- ...item 1...,
-- :
-- ...item n...
-- ],
-- \"paging\": {
-- \"previous\": \"http://...link to previous page...\",
-- \"next\": \"http://...link to next page...\"
-- }
-- }
-- @
--
-- Only the @\"data\"@ field is required, the others may or may
-- not appear.
--
-- A @Pager a@ datatype encodes such result where each item has
-- type @a@. You may use functions 'fetchNextPage' and
-- 'fetchPreviousPage' to navigate through the results.
data Pager a =
Pager {
pagerData :: [a]
, pagerPrevious :: Maybe String
, pagerNext :: Maybe String
} deriving (Eq, Ord, Show, Read, Typeable)
instance A.FromJSON a => A.FromJSON (Pager a) where
parseJSON (A.Object v) =
let paging f = v A..:? "paging" >>= maybe (return Nothing) (A..:? f)
in Pager <$> v A..: "data"
<*> paging "previous"
<*> paging "next"
parseJSON _ = mzero
-- | Tries to fetch the next page of a 'Pager'. Returns
-- 'Nothing' whenever the current @Pager@ does not have a
-- 'pagerNext'.
fetchNextPage :: (R.MonadResource m, MonadBaseControl IO m, A.FromJSON a) =>
Pager a -> FacebookT anyAuth m (Maybe (Pager a))
fetchNextPage = fetchHelper pagerNext
-- | Tries to fetch the previous page of a 'Pager'. Returns
-- 'Nothing' whenever the current @Pager@ does not have a
-- 'pagerPrevious'.
fetchPreviousPage :: (R.MonadResource m, MonadBaseControl IO m, A.FromJSON a) =>
Pager a -> FacebookT anyAuth m (Maybe (Pager a))
fetchPreviousPage = fetchHelper pagerPrevious
-- | (Internal) See 'fetchNextPage' and 'fetchPreviousPage'.
fetchHelper :: (R.MonadResource m, MonadBaseControl IO m, A.FromJSON a) =>
(Pager a -> Maybe String) -> Pager a -> FacebookT anyAuth m (Maybe (Pager a))
fetchHelper pagerRef pager =
case pagerRef pager of
Nothing -> return Nothing
Just url -> do
req <- liftIO (H.parseUrl url)
Just <$> (asJson =<< fbhttp req { H.redirectCount = 3 })
-- | Tries to fetch all next pages and returns a 'C.Source' with
-- all results. The 'C.Source' will include the results from
-- this page as well. Previous pages will not be considered.
-- Next pages will be fetched on-demand.
fetchAllNextPages ::
(Monad m, MonadResourceBase n, A.FromJSON a) =>
Pager a -> FacebookT anyAuth m (C.Source n a)
fetchAllNextPages = fetchAllHelper pagerNext
-- | Tries to fetch all previous pages and returns a 'C.Source'
-- with all results. The 'C.Source' will include the results
-- from this page as well. Next pages will not be
-- considered. Previous pages will be fetched on-demand.
fetchAllPreviousPages ::
(Monad m, MonadResourceBase n, A.FromJSON a) =>
Pager a -> FacebookT anyAuth m (C.Source n a)
fetchAllPreviousPages = fetchAllHelper pagerPrevious
-- | (Internal) See 'fetchAllNextPages' and 'fetchAllPreviousPages'.
fetchAllHelper ::
(Monad m, MonadResourceBase n, A.FromJSON a) =>
(Pager a -> Maybe String) -> Pager a -> FacebookT anyAuth m (C.Source n a)
fetchAllHelper pagerRef pager = do
manager <- getManager
let go (x:xs) mnext = C.yield x >> go xs mnext
go [] Nothing = return ()
go [] (Just next) = do
req <- liftIO (H.parseUrl next)
let get = fbhttpHelper manager req { H.redirectCount = 3 }
start =<< lift (R.runResourceT $ asJsonHelper =<< get)
start p = go (pagerData p) $! pagerRef p
return (start pager)