improve tailVerify

Wait for the file to get modified, not only opened. This way, if a
remote does not support resuming, and opens a new file over top of the
existing file, it will wait until that remote starts writing, and open
the file it's writing to, not the old file.

Sponsored-by: Dartmouth College's DANDI project
This commit is contained in:
Joey Hess 2021-08-16 12:42:44 -04:00
parent e46a7dff6f
commit e0b7f391bd
No known key found for this signature in database
GPG key ID: DB12DB0FF05F8F38

View file

@ -172,73 +172,57 @@ tailVerify iv f finished =
failIncremental iv failIncremental iv
return Nothing return Nothing
where where
f' = fromRawFilePath f -- Watch the directory containing the file, and wait for
waitforfiletoexist i = tryNonAsync (openBinaryFile f' ReadMode) >>= \case -- the file to be modified. It's possible that the file already
Right h -> return (Just h) -- exists before the downloader starts, but it replaces it instead
Left _ -> do -- of resuming, and waiting for modification deals with such
hv <- newEmptyTMVarIO -- situations.
wd <- inotifycreate i $ inotifydirchange i cont =
tryNonAsync (openBinaryFile f' ReadMode) >>= \case INotify.addWatch i [INotify.Modify] dir $ \case
Right h ->
unlessM (atomically $ tryPutTMVar hv h) $
hClose h
Left _ -> return ()
-- Wait for the file to appear, or for a signal
-- that the file is finished being written.
--
-- The TMVar is left full to prevent the file
-- being opened again if the inotify event
-- fires more than once.
v <- atomically $
(Just <$> readTMVar hv)
`orElse`
((const Nothing) <$> readTMVar finished)
void $ tryNonAsync $ INotify.removeWatch wd
return v
inotifycreate i cont = INotify.addWatch i evs (P.takeDirectory f) $ \case
-- Ignore changes to other files in the directory. -- Ignore changes to other files in the directory.
INotify.Created { INotify.filePath = fn }
| fn /= basef -> noop
INotify.MovedIn { INotify.filePath = fn }
| fn /= basef -> noop
INotify.Opened { INotify.maybeFilePath = fn }
| fn /= Just basef -> noop
INotify.Modified { INotify.maybeFilePath = fn } INotify.Modified { INotify.maybeFilePath = fn }
| fn /= Just basef -> noop | fn == Just basef -> cont
_ -> cont _ -> noop
where where
evs = (dir, basef) = P.splitFileName f
[ INotify.Create
, INotify.MoveIn inotifyfilechange i = INotify.addWatch i [INotify.Modify] f . const
, INotify.Move
, INotify.Open
, INotify.Modify
]
basef = P.takeFileName f
go = INotify.withINotify $ \i -> do go = INotify.withINotify $ \i -> do
h <- waitforfiletoexist i modified <- newEmptyTMVarIO
tryNonAsync (go' i h) >>= \case let signalmodified = atomically $ void $ tryPutTMVar modified ()
Right r -> return r wd <- inotifydirchange i signalmodified
Left _ -> do let cleanup = void . tryNonAsync . INotify.removeWatch
maybe noop hClose h let stop w = do
cleanup w
failIncremental iv failIncremental iv
return Nothing return Nothing
waitopen modified >>= \case
go' i (Just h) = do Nothing -> stop wd
modified <- newEmptyTMVarIO Just h -> do
wd <- INotify.addWatch i [INotify.Modify] f $ \_event -> cleanup wd
atomically $ void $ tryPutTMVar modified () wf <- inotifyfilechange i signalmodified
r <- follow h modified tryNonAsync (follow h modified) >>= \case
void $ tryNonAsync $ INotify.removeWatch wd Left _ -> do
hClose h
stop wf
Right r -> do
cleanup wf
return r return r
-- File never showed up, but we've been told it's done being waitopen modified = do
-- written to. v <- atomically $
go' _ Nothing = do (Just <$> takeTMVar modified)
failIncremental iv `orElse`
return Nothing ((const Nothing) <$> takeTMVar finished)
case v of
Just () -> tryNonAsync (openBinaryFile (fromRawFilePath f) ReadMode) >>= \case
Right h -> return (Just h)
-- Failed to open, wait for next
-- modification and try again.
Left _ -> waitopen modified
-- finished without the file being modified
Nothing -> return Nothing
follow h modified = do follow h modified = do
b <- S.hGetNonBlocking h chunk b <- S.hGetNonBlocking h chunk