{-| Module : Neptune.Session Description : Neptune Client Copyright : (c) Jiasen Wu, 2020 License : BSD-3-Clause -} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE ExistentialQuantification #-} {-# LANGUAGE Rank2Types #-} {-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE TemplateHaskell #-} module Neptune.Session where import Control.Concurrent (ThreadId) import Control.Concurrent.Event as E (Event) import Control.Lens import Control.Monad.Except (MonadError (throwError)) import qualified Data.Aeson as Aeson import Data.Aeson.Lens import qualified Data.ByteString.Base64 as Base64 import Data.Time.Clock (UTCTime) import Data.Time.Clock.POSIX (utcTimeToPOSIXSeconds) import qualified Network.HTTP.Client as NH import RIO hiding (Lens', (^.)) import qualified RIO.Text as T import System.Envy (FromEnv (..), Parser, env) import Neptune.Backend.Core import Neptune.Backend.MimeTypes import Neptune.Backend.Model hiding (Experiment, Parameter) import Neptune.OAuth -- | Decoded client token data ClientToken = ClientToken { _ct_token :: Text -- ^ user secret , _ct_api_address :: Text -- ^ neptune api address , _ct_api_url :: Text -- ^ neptune api address , _ct_api_key :: Text -- ^ neptune api key (not used) } deriving (Generic, Show) instance FromEnv ClientToken where fromEnv _ = do token <- env "NEPTUNE_API_TOKEN" jsraw <- either throwError return $ Base64.decode $ T.encodeUtf8 token js <- either throwError return $ Aeson.eitherDecodeStrict' jsraw :: Parser Aeson.Value return $ ClientToken token (js ^?! key "api_address" . _String) (js ^?! key "api_url" . _String) (js ^?! key "api_key" . _String) type Dispatcher = forall req contentType res accept . (Produces req accept, MimeUnrender accept res, MimeType contentType, HasCallStack) => NeptuneBackendRequest req contentType res accept -> IO res -- | Neptune session. It contains all necessary information to communicate the -- server. data NeptuneSession = NeptuneSession { _neptune_http_manager :: NH.Manager , _neptune_client_token :: ClientToken , _neptune_config :: NeptuneBackendConfig , _neptune_oauth2 :: MVar OAuth2Session , _neptune_oauth2_refresh :: ThreadId -- ^ Background thread for updating OAuth2 token , _neptune_project :: ProjectWithRoleDTO -- ^ Active project , _neptune_dispatch :: Dispatcher -- ^ Dispatching function for http requests } data Experiment = Experiment { _exp_experiment_id :: ExperimentId -- ^ Experiment Id , _exp_outbound_q :: TChan DataPointAny -- ^ Output queue , _exp_user_channels :: ChannelHashMap -- ^ Active output channels , _exp_stop_flag :: E.Event -- ^ Event flag to indicate the end of the session , _exp_transmitter_flag :: E.Event -- ^ Event flag to indicate completion of transmission , _exp_transmitter :: ThreadId -- ^ Background thread for transmission , _exp_abort_handler :: ThreadId } class (Typeable a, Show a) => NeptDataType a where neptChannelType :: Proxy a -> ChannelTypeEnum toNeptPoint :: DataPoint a -> Point -- | Type-safe data channel newtype DataChannel a = DataChannel Text {- ^ Channel Id -} deriving Show -- | Data channel of any type data DataChannelAny = forall a . NeptDataType a => DataChannelAny (DataChannel a) deriving instance Show DataChannelAny -- | Hashmap of all channels in use type ChannelHashMap = TVar (HashMap Text DataChannelAny) -- | Type-safe data point data DataPoint a = DataPoint { _dpt_name :: Text , _dpt_timestamp :: UTCTime , _dpt_value :: a } deriving Show -- | Data point of any type data DataPointAny = forall a . NeptDataType a => DataPointAny (DataPoint a) deriving instance Show DataPointAny makeLenses ''ClientToken makeLensesFor [("_neptune_http_manager", "neptune_http_manager") ,("_neptune_client_token", "neptune_client_token") ,("_neptune_config", "neptune_config") ,("_neptune_oauth2", "neptune_oauth2") ,("_neptune_oauth2_refresh", "neptune_oauth2_refresh") ,("_neptune_project", "neptune_project")] ''NeptuneSession makeLenses ''Experiment makeLenses ''DataPoint -- | Lens to get/set '_dpt_name' for 'DataPointAny' dpt_name_A :: Lens' DataPointAny Text dpt_name_A f (DataPointAny (DataPoint n t v)) = let set = (\n -> DataPointAny (DataPoint n t v)) in set <$> f n -- | Lens to get/set '_dpt_timestamp' for 'DataPointAny' dpt_timestamp_A :: Lens' DataPointAny UTCTime dpt_timestamp_A f (DataPointAny (DataPoint n t v)) = let set = (\t -> DataPointAny (DataPoint n t v)) in set <$> f t instance NeptDataType Double where neptChannelType _ = ChannelTypeEnum'Numeric toNeptPoint dat = let t = floor $ utcTimeToPOSIXSeconds (dat ^. dpt_timestamp) * 1000 y = mkY{ yNumericValue = dat ^. dpt_value . re _Just } in mkPoint t y