packages feed

imm-2.1.0.0: src/main/Database/Async.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeFamilies      #-}
module Database.Async (withAsyncHandle) where

import           Database.Handle
import           Output                        (putDocLn)
import qualified Output

import           Control.Concurrent.Async
import           Control.Concurrent.STM.TMChan
import           Control.Exception.Safe
import           Imm.Logger                    (LogLevel (..), log)
import qualified Imm.Logger                    as Logger
import           Imm.Pretty


-- | Run an existing database handle in a separate thread
withAsyncHandle :: m ~ IO => Logger.Handle m -> Output.Handle m -> Handle m -> (Handle m -> m ()) -> m ()
withAsyncHandle logger output database f = do
  channel <- newTMChanIO

  thread <- async $ fix $ \recurse -> do
    maybeMessage <- atomically $ readTMChan channel
    forM_ maybeMessage $ \message -> do
      interpret database message
      recurse

  log logger Debug "Database thread started"
  catchAny (f $ command channel) $ \e -> do
    log logger Error $ pretty $ displayException e
    putDocLn output $ pretty $ displayException e
  log logger Debug "Closing database thread..."
  atomically (closeTMChan channel) >> wait thread

withMVar :: MonadIO m => TMChan a -> ((b -> m ()) -> a) -> m b
withMVar channel message = do
  result <- newEmptyMVar
  atomically $ writeTMChan channel (message $ putMVar result)
  takeMVar result

withMVarE :: (MonadIO m, MonadThrow m, Exception e)
  => TMChan a -> ((Either e b -> m ()) -> a) -> m b
withMVarE channel message = do
  result <- newEmptyMVar
  atomically $ writeTMChan channel (message $ putMVar result)
  takeMVar result >>= either throwM return

asyncCommand :: MonadIO m => TMChan a -> (m () -> a) -> m ()
asyncCommand channel message = do
  atomically $ writeTMChan channel (message $ return ())

command :: MonadIO m => MonadThrow m => TMChan (HandleF (m ())) -> Handle m
command channel = Handle
  { _describeDatabase = withMVar channel DescribeDatabase
  , _fetchAllFeeds = withMVar channel FetchAllFeeds
  , _fetchFeed = withMVarE channel . FetchFeed
  , _fetchAllItems = withMVar channel FetchAllItems
  , _fetchItems = withMVar channel . FetchItems
  , _fetchItem = withMVarE channel . FetchItem
  , _updateFeedDefinition = asyncCommand channel . UpdateFeedDefinition
  , _updateFeedStatus = asyncCommand channel . UpdateFeedStatus
  , _updateItemStatus = asyncCommand channel . UpdateItemStatus
  , _insertFeed = withMVarE channel . InsertFeed
  , _insertItem = withMVar channel . InsertItem
  , _deleteFeed = asyncCommand channel . DeleteFeed
  , _purge = asyncCommand channel Purge
  , _commit = asyncCommand channel Commit
  }