@@ -127,17 +127,47 @@ data MonitorStateGlob
127127 ! MonitorStateGlobRel
128128 deriving (Show , Generic )
129129
130+ -- | Monitoring state for a 'Glob'. Constructors mirror those of Glob
130131data MonitorStateGlobRel
131- = MonitorStateGlobDirs
132+ = -- | Monitoring state for 'GlobDir'
133+ MonitorStateGlobDirs
132134 ! GlobPieces
135+ -- ^ Glob matching on subdirectory in current directory
133136 ! Glob
137+ -- ^ Glob tail matching on anything below subdirectory
134138 ! ModTime
135- ! [(FilePath , MonitorStateGlobRel )] -- invariant: sorted
136- | MonitorStateGlobFiles
139+ -- ^ Cached directory modification time
140+ ! [(FilePath , MonitorStateGlobRel )]
141+ -- ^ Per-file monitoring state.
142+ -- Invariant: sorted
143+ | -- | Monitoring state for 'GlobFile'
144+ MonitorStateGlobFiles
137145 ! GlobPieces
146+ -- ^ Glob matching on file in current directory
138147 ! ModTime
148+ -- ^ Cached directory modification time
139149 ! [(FilePath , MonitorStateFileStatus )] -- invariant: sorted
140- | MonitorStateGlobDirTrailing
150+
151+ -- ^ Per-file monitoring state.
152+ -- Invariant: sorted
153+ | -- | Monitoring state for 'GlobDirRecursive'
154+ MonitorStateGlobRecursive
155+ ! GlobPieces
156+ -- ^ Glob matching on file in current directory subtree (current
157+ -- directory and all of its descendants).
158+ ! ModTime
159+ -- ^ Cached directory modification time
160+ ! [(FilePath , MonitorStateFileStatus )]
161+ -- ^ Per-file monitoring state for files immediately below the current
162+ -- directory.
163+ -- Invariant: sorted
164+ ! [(FilePath , MonitorStateGlobRel )]
165+ -- ^ Monitoring state for immediate subdirectories. Transient
166+ -- subdirectories are represented recursively within these.
167+ -- Invariant: sorted
168+ | -- | Monitoring state for 'GlobDirTrailing'
169+ -- (Trivial, because there is no data in 'GlobDirTrailing')
170+ MonitorStateGlobDirTrailing
141171 deriving (Show , Generic )
142172
143173instance Binary MonitorStateGlob
@@ -161,10 +191,18 @@ reconstructMonitorFilePaths (MonitorStateFileSet singlePaths globPaths) =
161191 getGlobPath (MonitorStateGlob kindfile kinddir root gstate) =
162192 MonitorFileGlob kindfile kinddir $
163193 RootedGlob root $
164- case gstate of
165- MonitorStateGlobDirs glob globs _ _ -> GlobDir glob globs
166- MonitorStateGlobFiles glob _ _ -> GlobFile glob
167- MonitorStateGlobDirTrailing -> GlobDirTrailing
194+ monitorStateGlobRelGlob gstate
195+
196+ -- | Reconstruct a 'Glob' from a 'MonitorStateGlobRel'. This simply erases the
197+ -- additional information in 'MonitorStateGlobRel' added via
198+ -- 'buildMonitorStateGlobRel'.
199+ monitorStateGlobRelGlob :: MonitorStateGlobRel -> Glob
200+ monitorStateGlobRelGlob gstate =
201+ case gstate of
202+ MonitorStateGlobDirs glob globs _ _ -> GlobDir glob globs
203+ MonitorStateGlobFiles glob _ _ -> GlobFile glob
204+ MonitorStateGlobRecursive glob _ _ _ -> GlobDirRecursive glob
205+ MonitorStateGlobDirTrailing -> GlobDirTrailing
168206
169207------------------------------------------------------------------------------
170208-- Checking the status of monitored files
@@ -522,22 +560,84 @@ probeMonitorStateGlob
522560 MonitorStateGlob kindfile kinddir globroot
523561 <$> probeMonitorStateGlobRel kindfile kinddir root " " glob
524562
525- probeMonitorStateGlobRel
563+ probeMonitorStateFiles
564+ :: FilePath
565+ -- ^ root path
566+ -> FilePath
567+ -- ^ path of the directory we are
568+ -- looking in relative to @root@
569+ -> GlobPieces
570+ -- ^ file glob to filter monitored files
571+ -> ModTime
572+ -- ^ cached directory modification time
573+ -> [(FilePath , MonitorStateFileStatus )]
574+ -> ChangedM (ModTime , [(FilePath , MonitorStateFileStatus )])
575+ probeMonitorStateFiles
576+ root
577+ dirName
578+ glob
579+ mtime
580+ children = do
581+ change <- liftIO $ checkDirectoryModificationTime (root </> dirName) mtime
582+ mtime' <- case change of
583+ Nothing -> return mtime
584+ Just mtime' -> do
585+ -- directory modification time changed:
586+ -- a matching file may have been added or deleted
587+ matches <-
588+ return . filter (matchGlobPieces glob)
589+ =<< liftIO (listDirectory (root </> dirName))
590+
591+ traverse_ probeMergeResult $
592+ mergeBy
593+ (\ (path1, _) path2 -> compare path1 path2)
594+ children
595+ (sort matches)
596+ return mtime'
597+
598+ -- Check that none of the children have changed
599+ for_ children $ \ (file, status) ->
600+ probeMonitorStateFileStatus root (dirName </> file) status
601+
602+ return (mtime', children)
603+ where
604+ -- Again, we don't force a cache rewrite with 'cacheChanged', but we do use
605+ -- the new mtime' if any.
606+
607+ probeMergeResult
608+ :: MergeResult (FilePath , MonitorStateFileStatus ) FilePath
609+ -> ChangedM ()
610+ probeMergeResult mr = case mr of
611+ InBoth _ _ -> return ()
612+ -- this is just to be able to accurately report which file changed:
613+ OnlyInLeft (path, _) -> somethingChanged (dirName </> path)
614+ OnlyInRight path -> somethingChanged (dirName </> path)
615+
616+ probeMonitorStateDirs
526617 :: MonitorKindFile
527618 -> MonitorKindDir
528619 -> FilePath
529620 -- ^ root path
530621 -> FilePath
531622 -- ^ path of the directory we are
532623 -- looking in relative to @root@
533- -> MonitorStateGlobRel
534- -> ChangedM MonitorStateGlobRel
535- probeMonitorStateGlobRel
624+ -> Maybe GlobPieces
625+ -- ^ optional glob to filter filenames by
626+ -> Glob
627+ -- ^ glob to filter subdirectories by
628+ -> ModTime
629+ -- ^ cached directory modification time
630+ -> [(FilePath , MonitorStateGlobRel )]
631+ -> ChangedM (ModTime , [(FilePath , MonitorStateGlobRel )])
632+ probeMonitorStateDirs
536633 kindfile
537634 kinddir
538635 root
539636 dirName
540- (MonitorStateGlobDirs glob globPath mtime children) = do
637+ globMaybe
638+ globPath
639+ mtime
640+ children = do
541641 change <- liftIO $ checkDirectoryModificationTime (root </> dirName) mtime
542642 case change of
543643 Nothing -> do
@@ -554,7 +654,7 @@ probeMonitorStateGlobRel
554654 return (fname, fstate')
555655 | (fname, fstate) <- children
556656 ]
557- return $! MonitorStateGlobDirs glob globPath mtime children'
657+ return $! ( mtime, children')
558658 Just mtime' -> do
559659 -- directory modification time changed:
560660 -- a matching subdir may have been added or deleted
@@ -564,7 +664,7 @@ probeMonitorStateGlobRel
564664 let subdir = root </> dirName </> entry
565665 in liftIO $ doesDirectoryExist subdir
566666 )
567- . filter (matchGlobPieces glob)
667+ . maybe id ( filter . matchGlobPieces) globMaybe
568668 =<< liftIO (listDirectory (root </> dirName))
569669
570670 children' <-
@@ -573,7 +673,7 @@ probeMonitorStateGlobRel
573673 (\ (path1, _) path2 -> compare path1 path2)
574674 children
575675 (sort matches)
576- return $! MonitorStateGlobDirs glob globPath mtime' children'
676+ return $! ( mtime', children')
577677 where
578678 -- Note that just because the directory has changed, we don't force
579679 -- a cache rewrite with 'cacheChanged' since that has some cost, and
@@ -626,17 +726,56 @@ probeMonitorStateGlobRel
626726 fstate
627727 return (path, fstate')
628728
629- -- \| Does a 'MonitorStateGlob' have any relevant files within it?
630- allMatchingFiles :: FilePath -> MonitorStateGlobRel -> [FilePath ]
631- allMatchingFiles dir (MonitorStateGlobFiles _ _ entries) =
729+ allMatchingFilesFromGlobFiles :: FilePath -> [(FilePath , a )] -> [FilePath ]
730+ allMatchingFilesFromGlobFiles dir entries =
632731 [dir </> fname | (fname, _) <- entries]
633- allMatchingFiles dir (MonitorStateGlobDirs _ _ _ entries) =
732+
733+ allMatchingFilesFromGlobDirs :: FilePath -> [(FilePath , MonitorStateGlobRel )] -> [FilePath ]
734+ allMatchingFilesFromGlobDirs dir entries =
634735 [ res
635736 | (subdir, fstate) <- entries
636737 , res <- allMatchingFiles (dir </> subdir) fstate
637738 ]
739+
740+ -- \| Does a 'MonitorStateGlob' have any relevant files within it?
741+ allMatchingFiles :: FilePath -> MonitorStateGlobRel -> [FilePath ]
742+ allMatchingFiles dir (MonitorStateGlobFiles _ _ entries) =
743+ allMatchingFilesFromGlobFiles dir entries
744+ allMatchingFiles dir (MonitorStateGlobDirs _ _ _ entries) =
745+ allMatchingFilesFromGlobDirs dir entries
746+ allMatchingFiles dir (MonitorStateGlobRecursive _ _ fileEntries dirEntries) =
747+ allMatchingFilesFromGlobFiles dir fileEntries
748+ ++ allMatchingFilesFromGlobDirs dir dirEntries
638749 allMatchingFiles dir MonitorStateGlobDirTrailing =
639750 [dir]
751+
752+ probeMonitorStateGlobRel
753+ :: MonitorKindFile
754+ -> MonitorKindDir
755+ -> FilePath
756+ -- ^ root path
757+ -> FilePath
758+ -- ^ path of the directory we are
759+ -- looking in relative to @root@
760+ -> MonitorStateGlobRel
761+ -> ChangedM MonitorStateGlobRel
762+ probeMonitorStateGlobRel
763+ kindfile
764+ kinddir
765+ root
766+ dirName
767+ (MonitorStateGlobDirs glob globPath mtime children) = do
768+ (mtime', children') <-
769+ probeMonitorStateDirs
770+ kindfile
771+ kinddir
772+ root
773+ dirName
774+ (Just glob)
775+ globPath
776+ mtime
777+ children
778+ return $! MonitorStateGlobDirs glob globPath mtime' children'
640779probeMonitorStateGlobRel
641780 _
642781 _
@@ -677,6 +816,33 @@ probeMonitorStateGlobRel
677816 -- this is just to be able to accurately report which file changed:
678817 OnlyInLeft (path, _) -> somethingChanged (dirName </> path)
679818 OnlyInRight path -> somethingChanged (dirName </> path)
819+ probeMonitorStateGlobRel
820+ kindfile
821+ kinddir
822+ root
823+ dirName
824+ (MonitorStateGlobRecursive glob mtime fileChildren dirChildren) = do
825+ -- For recursive globs, we check the file children first, then recurse
826+ -- into subdirectories, applying the same logic as 'MonitorStateGlobFiles'
827+ -- and 'MonitorStateGlobDirs', respectively.
828+ (_, fileChildren') <-
829+ probeMonitorStateFiles
830+ root
831+ dirName
832+ glob
833+ mtime
834+ fileChildren
835+ (mtime', dirChildren') <-
836+ probeMonitorStateDirs
837+ kindfile
838+ kinddir
839+ root
840+ dirName
841+ Nothing
842+ (GlobDirRecursive glob)
843+ mtime
844+ dirChildren
845+ return $! MonitorStateGlobRecursive glob mtime' fileChildren' dirChildren'
680846probeMonitorStateGlobRel _ _ _ _ MonitorStateGlobDirTrailing =
681847 return MonitorStateGlobDirTrailing
682848
@@ -916,7 +1082,37 @@ buildMonitorStateGlobRel
9161082 dirEntries <- listDirectory absdir
9171083 dirMTime <- getModTime absdir
9181084 case globPath of
919- GlobDirRecursive {} -> error " Monitoring directory-recursive globs (i.e. ../**/...) is currently unsupported"
1085+ GlobDirRecursive glob -> do
1086+ -- evaluate globPath' over the current directory
1087+ let files = filter (matchGlobPieces glob) dirEntries
1088+ filesStates <-
1089+ for (sort files) $ \ file -> do
1090+ fstate <-
1091+ buildMonitorStateFile
1092+ mstartTime
1093+ hashcache
1094+ kindfile
1095+ kinddir
1096+ root
1097+ (dir </> file)
1098+ return (file, fstate)
1099+ -- evaluate globPath' over every subdirectory
1100+ subdirs <-
1101+ filterM (\ subdir -> doesDirectoryExist (absdir </> subdir)) dirEntries
1102+ subdirStates <-
1103+ for (sort subdirs) $ \ subdir -> do
1104+ fstate <-
1105+ buildMonitorStateGlobRel
1106+ mstartTime
1107+ hashcache
1108+ kindfile
1109+ kinddir
1110+ root
1111+ (dir </> subdir)
1112+ globPath
1113+ return (subdir, fstate)
1114+
1115+ return $! MonitorStateGlobRecursive glob dirMTime filesStates subdirStates
9201116 GlobDir glob globPath' -> do
9211117 subdirs <-
9221118 filterM (\ subdir -> doesDirectoryExist (absdir </> subdir)) $
@@ -1015,16 +1211,27 @@ readCacheFileHashes monitor =
10151211 , (fpath, (mtime, hash)) <- collectGlobHashes " " gstate
10161212 ]
10171213
1018- collectGlobHashes :: FilePath -> MonitorStateGlobRel -> [(FilePath , (ModTime , HashValue ))]
1019- collectGlobHashes dir ( MonitorStateGlobDirs _ _ _ entries) =
1214+ collectDirHashes :: FilePath -> [( FilePath , MonitorStateGlobRel )] -> [(FilePath , (ModTime , HashValue ))]
1215+ collectDirHashes dir entries =
10201216 [ res
10211217 | (subdir, fstate) <- entries
10221218 , res <- collectGlobHashes (dir </> subdir) fstate
10231219 ]
1024- collectGlobHashes dir (MonitorStateGlobFiles _ _ entries) =
1220+
1221+ collectFileHashes :: FilePath -> [(FilePath , MonitorStateFileStatus )] -> [(FilePath , (ModTime , HashValue ))]
1222+ collectFileHashes dir entries =
10251223 [ (dir </> fname, (mtime, hash))
10261224 | (fname, MonitorStateFileHashed mtime hash) <- entries
10271225 ]
1226+
1227+ collectGlobHashes :: FilePath -> MonitorStateGlobRel -> [(FilePath , (ModTime , HashValue ))]
1228+ collectGlobHashes dir (MonitorStateGlobDirs _ _ _ entries) =
1229+ collectDirHashes dir entries
1230+ collectGlobHashes dir (MonitorStateGlobFiles _ _ entries) =
1231+ collectFileHashes dir entries
1232+ collectGlobHashes dir (MonitorStateGlobRecursive _ _ fileEntries dirEntries) =
1233+ collectFileHashes dir fileEntries
1234+ ++ collectDirHashes dir dirEntries
10281235 collectGlobHashes _dir MonitorStateGlobDirTrailing =
10291236 []
10301237
0 commit comments