@@ -18,8 +18,8 @@ module Glean.Impl.ConfigProvider (
1818
1919import Control.Concurrent
2020import Control.Exception
21+ import Data.Maybe (fromMaybe )
2122import qualified Data.ByteString as ByteString
22- import qualified Data.ByteString.Char8 as BC
2323import Data.ByteString (ByteString )
2424import Data.HashMap.Strict (HashMap )
2525import qualified Data.HashMap.Strict as HashMap
@@ -29,17 +29,21 @@ import Options.Applicative
2929import System.Directory
3030import System.FilePath
3131import System.IO.Error
32- import System.INotify
32+
33+ import qualified System.FSNotify as FSNotify
3334
3435import Util.Control.Exception
3536
3637import Glean.Util.ConfigProvider
3738
3839data ConfigAPI = ConfigAPI
39- { opts :: LocalConfigOptions
40- , inotify :: INotify
40+ { canonConfigDir :: FilePath
41+ , opts :: LocalConfigOptions
42+ -- ^ Canonicalized configuration directory (since the file watcher will
43+ -- likely canonicalize paths).
44+ , watchManager :: FSNotify. WatchManager
4145 , subscriptions ::
42- MVar (HashMap ConfigPath ( WatchDescriptor , [ByteString -> IO () ]) )
46+ MVar (HashMap ConfigPath [ByteString -> IO () ])
4347 }
4448
4549newtype LocalConfigOptions = LocalConfigOptions
@@ -57,6 +61,23 @@ newtype ConfigProviderException = ConfigProviderException Text
5761
5862instance Exception ConfigProviderException
5963
64+ -- | Whether to accept a FS event for a given path
65+ acceptEvent :: FSNotify. Event -> Bool
66+ acceptEvent (FSNotify. Added _path _time FSNotify. IsFile ) = True
67+ acceptEvent (FSNotify. Modified _path _time FSNotify. IsFile ) = True
68+ -- Included for documentation of intent
69+ acceptEvent (FSNotify. Removed _path _time _isDir) = False
70+ acceptEvent (FSNotify. ModifiedAttributes _path _time _isDir) = False
71+ acceptEvent _ = False
72+
73+ onEvent :: ConfigAPI -> FilePath -> IO ()
74+ onEvent ConfigAPI {.. } path = do
75+ -- Maybe weird symlink things going on? We don't know what to do, in any case
76+ callbacks <- fromMaybe [] . HashMap. lookup (Text. pack path) <$> readMVar subscriptions
77+ contents <- ByteString. readFile path
78+ mapM_ ($ contents) callbacks
79+ `catchAll` \ _ -> return ()
80+
6081instance ConfigProvider ConfigAPI where
6182 configOptions = do
6283 configDir <- optional $ strOption
@@ -70,46 +91,37 @@ instance ConfigProvider ConfigAPI where
7091 defaultConfigOptions = LocalConfigOptions { configDir = Nothing }
7192
7293 withConfigProvider opts f =
73- withINotify $ \ inotify -> do
94+ FSNotify. withManager $ \ watchManager -> do
7495 subs <- newMVar HashMap. empty
75- f (ConfigAPI opts inotify subs)
96+ -- fsnotify seems to give us canonicalized paths back; we would like to
97+ -- reason based on prefixes of them, so we need to canonicalize here.
98+ canonConfigDir <- canonicalizePath =<< getDir opts
99+ let cfg = ConfigAPI canonConfigDir opts watchManager subs
100+ _stopListening <-
101+ FSNotify. watchTree watchManager canonConfigDir acceptEvent (\ ev -> onEvent cfg (FSNotify. eventPath ev))
102+ f cfg
76103
77104 type Subscription ConfigAPI = LocalSubscription
78105
79106 subscribe cfg@ ConfigAPI {.. } path updated deserializer = do
80107 a <- get cfg path deserializer
81108 updated a
82- dir <- getDir opts
83- modifyMVar_ subscriptions $ \ hm -> do
84- let
85- changed contents =
86- deserialize path deserializer contents >>= updated
87- case HashMap. lookup path hm of
88- Just (watch, others) ->
89- return $ HashMap. insert path (watch, changed: others) hm
90- Nothing -> do
91- let file = BC. pack $ dir </> Text. unpack path
92- watch <- addWatch inotify [Modify ,MoveIn ,Create ] file $ \ _events -> do
93- callbacks <- withMVar subscriptions $ \ hm -> do
94- case HashMap. lookup path hm of
95- Nothing -> return []
96- Just (_, callbacks) -> return callbacks
97- contents <- ByteString. readFile (dir </> Text. unpack path)
98- mapM_ ($ contents) callbacks
99- `catchAll` \ _ -> return ()
100- return $ HashMap. insert path (watch, [changed]) hm
109+ let absPath = Text. pack $ canonConfigDir </> (Text. unpack path)
110+ modifyMVar_ subscriptions $ \ hm ->
111+ let changed contents =
112+ deserialize path deserializer contents >>= updated
113+ in pure $ HashMap. insertWith (<>) absPath [changed] hm
101114 return LocalSubscription
102115
103116 cancel _ _ = return () -- unimplemented for now
104117
105118 get ConfigAPI {.. } path deserializer = do
106- dir <- getDir opts
107- contents <- ByteString. readFile (dir </> Text. unpack path)
119+ contents <- ByteString. readFile (canonConfigDir </> Text. unpack path)
108120 `catch` \ e ->
109121 if isDoesNotExistError e
110122 then throwIO $ ConfigProviderException $
111123 " no config for " <> path <> " at " <>
112- Text. pack (dir </> Text. unpack path)
124+ Text. pack (canonConfigDir </> Text. unpack path)
113125 else
114126 throwIO e
115127 deserialize path deserializer contents
0 commit comments