@@ -41,6 +41,7 @@ module ProjectM36.Client
4141 Atom(..),
4242 Session,
4343 SessionId,
44+ NotificationCallback,
4445 AtomType(..)) where
4546import ProjectM36.Base hiding (inclusionDependencies) --defined in this module as well
4647import qualified ProjectM36.Base as B
@@ -77,6 +78,7 @@ import ProjectM36.Sessions
7778import ListT
7879import Data.Binary (Binary)
7980import GHC.Generics (Generic)
81+import System.IO (stderr, hPutStrLn)
8082--import Debug.Trace
8183
8284type Hostname = String
@@ -85,9 +87,16 @@ type Port = Word16
8587
8688type DatabaseName = String
8789
90+-- | The type for notifications callbacks in the client. When a registered notification fires due to a changed relational expression evaluation, the server propagates the notifications to the clients in the form of the callback.
91+type NotificationCallback = NotificationName -> EvaluatedNotification -> IO ()
92+
93+-- | The empty notification callback ignores all callbacks.
94+emptyNotificationCallback :: NotificationCallback
95+emptyNotificationCallback _ _ = pure ()
96+
8897-- | Construct a 'ConnectionInfo' to describe how to make the 'Connection'.
89−data ConnectionInfo = InProcessConnectionInfo PersistenceStrategy |
90− RemoteProcessConnectionInfo DatabaseName NodeId
98+data ConnectionInfo = InProcessConnectionInfo PersistenceStrategy NotificationCallback |
99+ RemoteProcessConnectionInfo DatabaseName NodeId NotificationCallback
91100
92101type EvaluatedNotifications = M.Map NotificationName EvaluatedNotification
93102
@@ -115,7 +124,7 @@ defaultHeadName :: HeadName
115124defaultHeadName = "master"
116125
117126defaultRemoteConnectionInfo :: ConnectionInfo
118−defaultRemoteConnectionInfo = RemoteProcessConnectionInfo defaultDatabaseName (createNodeId "127.0.0.1" defaultServerPort)
127+defaultRemoteConnectionInfo = RemoteProcessConnectionInfo defaultDatabaseName (createNodeId "127.0.0.1" defaultServerPort) emptyNotificationCallback
119128
120129-- | The 'Connection' represents either local or remote access to a database. All operations flow through the connection.
121130type ClientNodes = STMSet.Set ProcessId
@@ -141,28 +150,31 @@ commonLocalNode = do
141150 Left err -> pure (Left $ IOExceptionError err)
142151 Right localTransport -> newLocalNode localTransport initRemoteTable >>= pure . Right
143152
144−notificationListener :: Process ()
145−notificationListener = do
153+notificationListener :: NotificationCallback -> Process ()
154+notificationListener callback = do
146155 pid <- getSelfPid
147156 liftIO $ putStrLn $ "LISTENER THREAD START " ++ show pid
148157 _ <- forever $ do
149158 receiveWait [
150− match (\(NotificationMessage eNots) -> say $ "NOTIFICATION: " ++ show eNots)
159+ match (\(NotificationMessage eNots) -> do
160+ --say $ "NOTIFICATION: " ++ show eNots
161+ liftIO $ mapM_ (uncurry callback) (M.toList eNots)
162+ )
151163 ]
152164 say "LISTENER THREAD EXIT"
153165 pure ()
154166
155−startNotificationListener :: IO (ProcessId)
156−startNotificationListener = do
167+startNotificationListener :: NotificationCallback -> IO (ProcessId)
168+startNotificationListener callback = do
157169 eLocalNode <- commonLocalNode
158170 case eLocalNode of
159171 Left err -> error ("Failed to start local notification listener: " ++ show err)
160− Right localNode -> forkProcess localNode notificationListener
172+ Right localNode -> forkProcess localNode (notificationListener callback)
161173
162174-- | To create a 'Connection' to a remote or local database, create a connectionInfo and call 'connectProjectM36'.
163175connectProjectM36 :: ConnectionInfo -> IO (Either ConnectionError Connection)
164176--create a new in-memory database/transaction graph
165−connectProjectM36 (InProcessConnectionInfo strat) = do
177+connectProjectM36 (InProcessConnectionInfo strat notificationCallback) = do
166178 freshUUID <- nextRandom
167179 let bootstrapContext = RE.basicDatabaseContext
168180 freshGraph = bootstrapTransactionGraph freshUUID bootstrapContext
@@ -172,15 +184,17 @@ connectProjectM36 (InProcessConnectionInfo strat) = do
172184 graphTvar <- newTVarIO freshGraph
173185 clientNodes <- STMSet.newIO
174186 sessions <- STMMap.newIO
175− --notificationPid <- startNotificationListener
176− return $ Right $ InProcessConnection strat clientNodes sessions graphTvar
177− MinimalPersistence dbdir -> connectPersistentProjectM36 strat NoDiskSync dbdir freshGraph
178− CrashSafePersistence dbdir -> connectPersistentProjectM36 strat FsyncDiskSync dbdir freshGraph
187+ notificationPid <- startNotificationListener notificationCallback
188+ let conn = InProcessConnection strat clientNodes sessions graphTvar
189+ addClientNode conn notificationPid
190+ pure (Right conn)
191+ MinimalPersistence dbdir -> connectPersistentProjectM36 strat NoDiskSync dbdir freshGraph notificationCallback
192+ CrashSafePersistence dbdir -> connectPersistentProjectM36 strat FsyncDiskSync dbdir freshGraph notificationCallback
179193
180−connectProjectM36 (RemoteProcessConnectionInfo databaseName serverNodeId) = do
194+connectProjectM36 (RemoteProcessConnectionInfo databaseName serverNodeId notificationCallback) = do
181195 connStatus <- newEmptyMVar
182196 eLocalNode <- commonLocalNode
183− notificationListenerPid <- startNotificationListener
197+ notificationListenerPid <- startNotificationListener notificationCallback
184198 let dbName = remoteDBLookupName databaseName
185199 putStrLn $ show serverNodeId ++ " " ++ dbName
186200 case eLocalNode of
@@ -202,9 +216,10 @@ connectProjectM36 (RemoteProcessConnectionInfo databaseName serverNodeId) = do
202216connectPersistentProjectM36 :: PersistenceStrategy ->
203217 DiskSync ->
204218 FilePath ->
205− TransactionGraph ->
219+ TransactionGraph ->
220+ NotificationCallback ->
206221 IO (Either ConnectionError Connection)
207−connectPersistentProjectM36 strat sync dbdir freshGraph = do
222+connectPersistentProjectM36 strat sync dbdir freshGraph notificationCallback = do
208223 err <- setupDatabaseDir sync dbdir freshGraph
209224 case err of
210225 Just err' -> return $ Left (SetupDatabaseDirectoryError err')
@@ -216,8 +231,11 @@ connectPersistentProjectM36 strat sync dbdir freshGraph = do
216231 tvarGraph <- newTVarIO graph'
217232 sessions <- STMMap.newIO
218233 clientNodes <- STMSet.newIO
219− return $ Right $ InProcessConnection strat clientNodes sessions tvarGraph
220−
234+ let conn = InProcessConnection strat clientNodes sessions tvarGraph
235+ notificationPid <- startNotificationListener notificationCallback
236+ addClientNode conn notificationPid
237+ pure (Right conn)
238+
221239-- | Create a new session at the transaction UUID and return the session's Id.
222240createSessionAtCommit :: UUID -> Connection -> IO (Either RelationalError SessionId)
223241createSessionAtCommit commitUUID conn@(InProcessConnection _ _ _ _) = do