This commit is contained in:
Dmitry Zuikov 2024-08-05 17:56:10 +03:00
parent 0cf84e1c94
commit 2d91362466
2 changed files with 178 additions and 145 deletions

View File

@ -43,7 +43,7 @@ main = do
cli <- liftIO getArgs <&> unlines . fmap unwords . splitForms
>>= either (error.show) pure . parseTop
<&> \case
[] -> [mkList [mkSym "run", mkSym "."]]
[] -> [mkList [mkSym "run"]]
xs -> xs
let dict = makeDict do

View File

@ -1,6 +1,9 @@
{-# Language UndecidableInstances #-}
{-# Language AllowAmbiguousTypes #-}
{-# Language TemplateHaskell #-}
{-# Language MultiWayIf #-}
{-# Language PatternSynonyms #-}
{-# Language ViewPatterns #-}
module HBS2.Sync.Prelude
( module HBS2.Sync.Prelude
, module Exported
@ -252,6 +255,17 @@ data EntryDesc =
newtype AsSexp c a = AsSexp a
pattern TombEntry :: Entry -> Entry
pattern TombEntry e <- e@(DirEntry (EntryDesc { entryType = Tomb }) _)
pattern FileEntry :: Entry -> Entry
pattern FileEntry e <- e@(DirEntry (EntryDesc { entryType = File }) _)
pattern UpdatedFileEntry :: Word64 -> Entry -> Entry
pattern UpdatedFileEntry t e <- e@(DirEntry (EntryDesc { entryType = File
, entryRemoteHash = Nothing
, entryTimestamp = t }) _)
instance (IsContext c, ToSexp c w) => Pretty (AsSexp c w) where
pretty (AsSexp s) = pretty (toSexp @c s)
@ -327,9 +341,12 @@ runDirectory :: ( IsContext c
, HasClientAPI RefChanAPI UNIX m
, HasClientAPI StorageAPI UNIX m
, HasStorage m
, HasRunDir m
, Exception (BadFormException c)
) => FilePath -> RunM c m ()
runDirectory path = do
) => RunM c m ()
runDirectory = do
path <- getRunDir
runDir
`catch` \case
@ -353,168 +370,178 @@ runDirectory path = do
where
mergeNameConflicts a b = do
let (files1, dirs1) = Map.elems a & L.partition isFile
let (files2, dirs2) = Map.elems b & L.partition isFile
postEntryTx refchan path entry = do
let files3 = [ (entryPath x, x) | x <- files1 <> files2 ]
& Map.fromListWith merge
sto <- getStorage
let dirs = Map.fromList [ (entryPath x, x) | x <- dirs1 <> dirs2 ]
let files = [ (entryPath x, x) | x <- Map.elems files3 ]
env <- getRunDirEnv path >>= orThrow DirNotSet
creds <- view dirSyncCreds env & orThrow DirNotSet
tn <- newTVarIO ( mempty :: Map FilePath Int )
es <- forM files $ \(f, e) -> do
rch <- Client.getRefChanHead @UNIX refchan
>>= orThrow RefChanHeadNotFoundException
debug $ red "CHECK FILE" <+> pretty f
let p = entryPath entry
-- FIXME: dangerous!
lbs <- liftIO (LBS.readFile (path </> p))
if Map.member f dirs then
error $ show $ "RENAME FILE" <+> pretty f
else
pure (f,e)
let (dir,file) = splitFileName p
pure $ Map.unionWith merge (Map.fromListWith merge es) dirs
let meta = HM.fromList [ ("file-name", fromString file)
]
<> case dir of
"./" -> mempty
d -> HM.singleton "location" (fromString d)
let members = view refChanHeadReaders rch & HS.toList
-- FIXME: support-unencrypted?
when (L.null members) do
throwIO EncryptionKeysNotDefined
gk <- Symm.generateGroupKey @'HBS2Basic Nothing members
-- FIXME: survive-this-error?
href <- lift $ createTreeWithMetadata sto (Just gk) meta lbs
>>= orThrowPassIO
let tx = AnnotatedHashRef Nothing href
let spk = view peerSignPk creds
let ssk = view peerSignSk creds
let box = makeSignedBox @HBS2Basic spk ssk (LBS.toStrict $ serialise tx)
notice $ red "POST NEW REMOTE ENTRY" <+> pretty p <+> pretty href
postRefChanTx @UNIX refchan box
writeEntry path e = do
let p = entryPath e
let filePath = path </> p
sto <- getStorage
void $ runMaybeT do
h <- getEntryHash e & toMPlus
notice $ green "write entry" <+> pretty h <+> pretty (path </> p)
lbs <- lift (runExceptT (getTreeContents sto h))
>>= toMPlus
mkdir (dropFileName filePath)
liftIO $ UIO.withBinaryFileAtomic filePath WriteMode $ \fh -> do
LBS.hPutStr fh lbs >> hFlush fh
let ts = getEntryTimestamp e
let timestamp = posixSecondsToUTCTime (fromIntegral ts)
liftIO $ setModificationTime (path </> p) timestamp
freshIn :: FilePath -> Entry -> Map FilePath Entry -> Bool
freshIn p e state = do
let remote = Map.lookup p state
maybe1 remote True $ \r -> do
getEntryTimestamp e > getEntryTimestamp r
runDir = do
notice $ yellow "run directory" <+> pretty path
path <- getRunDir
error "NOT IMPLEMENTED YET"
env <- getRunDirEnv path >>= orThrow DirNotSet
-- trc <- newTVarIO Nothing
-- tsign <- newTVarIO Nothing
-- texcl <- newTQueueIO
-- tincl <- newTQueueIO
refchan <- view dirSyncRefChan env & orThrow RefChanNotSetException
-- atomically $ writeTQueue tincl "**"
fetchRefChan @UNIX refchan
-- ins <- liftIO (try @_ @IOError (readFile (path </> ".hbs2-sync/config")))
-- <&> fromRight mempty
-- <&> parseTop
-- <&> either mempty (fmap fixContext)
local <- getStateFromDir0 True
-- debug $ pretty ins
-- evalTop ins
merged <- mergeState local
-- incl <- atomically (flushTQueue tincl) <&> HS.fromList <&> HS.toList
-- excl <- atomically (flushTQueue texcl) <&> HS.fromList <&> HS.toList
let filesLast m = case mergedEntryType m of
Tomb -> 0
Dir -> 1
File -> 2
-- refchan <- readTVarIO trc
-- >>= orThrow RefChanNotSetException
-- liftIO $ print $ vcat (fmap (pretty . AsSexp @C) merged)
-- fetchRefChan @UNIX refchan
for_ (L.sortOn filesLast merged) $ \w -> do
case w of
N (p,TombEntry e) -> do
let fullPath = path </> p
notice $ green "removed entry" <+> pretty p
-- rch <- Client.getRefChanHead @UNIX refchan
-- >>= orThrow RefChanHeadNotFoundException
N (_,_) -> none
-- creds <- readTVarIO tsign
-- >>= orThrow SignKeyNotSet
M (f,t,e) -> do
notice $ green "move entry" <+> pretty f <+> pretty t
mv (path </> f) (path </> t)
notice $ green "post renamed entry tx" <+> pretty f
postEntryTx refchan path e
-- sto <- getClientAPI @StorageAPI @UNIX
-- <&> AnyStorage . StorageClient
E (p,UpdatedFileEntry _ e) -> do
let fullPath = path </> p
here <- liftIO $ doesFileExist fullPath
writeEntry path e
notice $ red "updated file entry" <+> pretty here <+> pretty p
postEntryTx refchan path e
-- debug $ "step 1" <+> "load state from refchan"
-- debug $ "step 1.1" <+> "initial state is empty"
-- debug $ "step 2" <+> "create local state"
-- debug $ "step 2.1" <+> "scan all files"
-- debug $ "step 2.2" <+> "extract all / directories"
E (p,e@(FileEntry _)) -> do
let fullPath = path </> p
here <- liftIO $ doesFileExist fullPath
d <- liftIO $ doesDirectoryExist fullPath
-- debug $ "step 3" <+> "merge states"
-- debug $ "step 3.1" <+> "generate merge actions"
-- debug $ "step 3.2" <+> "apply actions"
older <- if here then do
s <- getFileTimestamp fullPath
pure $ s < getEntryTimestamp e
else
pure False
-- let p0 = normalise path
when (not here || older) do
writeEntry path e
-- local <- getStateFromDir path incl excl
-- remote <- getStateFromRefChan refchan
-- merged <- mergeNameConflicts local remote
-- for_ (Map.toList merged) $ \(k,v) -> do
-- debug $ red "LOCAL MERGED" <+> pretty k <+> viaShow v
-- for_ (Map.toList merged) $ \(p,e) -> do
-- let filePath = path </> p
-- debug $ yellow "entry" <+> pretty p <+> viaShow e
-- debug $ red "FRESH:" <+> pretty p <+> pretty (freshIn p e local)
-- when (freshIn p e local && isFile e) $ void $ runMaybeT do
-- h <- getEntryHash e & toMPlus
-- notice $ red "WRITE NEW LOCAL ENTRY" <+> pretty path <+> pretty p <+> pretty (getEntryHash e)
-- lbs <- lift (runExceptT (getTreeContents sto h))
-- >>= toMPlus
-- mkdir (dropFileName filePath)
-- liftIO $ UIO.withBinaryFileAtomic filePath WriteMode $ \fh -> do
-- LBS.hPutStr fh lbs
-- let ts = getEntryTimestamp e
-- let timestamp = posixSecondsToUTCTime (fromIntegral ts)
-- liftIO $ setModificationTime (path </> p) timestamp
-- actuallyFile <- liftIO $ doesFileExist filePath
-- when (freshIn p e remote && actuallyFile) do
-- -- FIXME: dangerous!
-- lbs <- liftIO (LBS.readFile (path </> p))
-- let (dir,file) = splitFileName p
-- let meta = HM.fromList [ ("file-name", fromString file)
-- ]
-- <> case dir of
-- "./" -> mempty
-- d -> HM.singleton "location" (fromString d)
-- let members = view refChanHeadReaders rch & HS.toList
-- -- FIXME: support-unencrypted?
-- when (L.null members) do
-- throwIO EncryptionKeysNotDefined
-- gk <- Symm.generateGroupKey @'HBS2Basic Nothing members
-- -- FIXME: survive-this-error?
-- href <- lift $ createTreeWithMetadata sto (Just gk) meta lbs
-- >>= orThrowPassIO
-- let tx = AnnotatedHashRef Nothing href
-- let spk = view peerSignPk creds
-- let ssk = view peerSignSk creds
-- -- let box = makeSignedBox @HBS2Basic spk ssk (LBS.toStrict $ serialise tx)
-- notice $ red "POST NEW REMOTE ENTRY" <+> pretty p <+> pretty href
-- postRefChanTx @UNIX refchan box
E (p,_) -> do
notice $ "skip entry" <+> pretty (path </> p)
merge :: Entry -> Entry -> Entry
merge a b = do
if getEntryTimestamp a > getEntryTimestamp b then a else b
if | getEntryTimestamp a > getEntryTimestamp b -> a
| isFile a && isDir b -> a
| isFile b && isDir a -> b
| getEntryTimestamp a == getEntryTimestamp b ->
case (getEntryHash a, getEntryHash b) of
(Nothing,Nothing) -> b
(Just _,Nothing) -> a
(Nothing,Just _) -> b
(Just _, Just _) -> b
| otherwise -> b
data Merged = N (FilePath, Entry)
| E (FilePath, Entry)
| M (FilePath,FilePath,Entry)
{-# COMPLETE N,E,M #-}
pattern MergedEntryType :: EntryType -> Merged
pattern MergedEntryType t <- ( mergedEntryType -> t )
mergedEntryType :: Merged -> EntryType
mergedEntryType = \case
N (_,DirEntry d _) -> entryType d
E (_,DirEntry d _) -> entryType d
M (_,_,DirEntry d _) -> entryType d
instance IsContext c => ToSexp c Merged where
toSexp = \case
N (_, e) -> mkForm @c "N" [toSexp e]
E (_, e) -> mkForm @c "E" [toSexp e]
M (o, t, e) -> mkForm @c "M" [toSexp e,mkSym o,mkSym t]
mergeState :: MonadUnliftIO m
=> [(FilePath, Entry)]
-> m [(FilePath, Entry)]
-> m [Merged]
mergeState orig = do
@ -530,10 +557,10 @@ mergeState orig = do
for_ (Map.toList files) $ \(p,e@(DirEntry d _)) -> do
if Map.member p dirs then do
let new = uniqName names p
S.yield (new, DirEntry d new)
S.yield (p, makeTomb now p)
S.yield $ M (p, new, DirEntry d new)
S.yield $ N (p, makeTomb now p)
else
S.yield (p,e)
S.yield $ E (p,e)
where
uniqName names0 name = do
@ -582,6 +609,9 @@ getStateFromDir0 seed = do
getStateFromDir seed dir incl excl
where
-- onlyLocal x = Map.toList $ Map.fromListWith merge x
getStateFromDir :: ( MonadIO m
, HasClientAPI RefChanAPI UNIX m
, HasClientAPI StorageAPI UNIX m
@ -833,7 +863,7 @@ syncEntries = do
merged <- mergeState state
liftIO $ print $ vcat (fmap (pretty . AsSexp @C . snd) merged)
liftIO $ print $ vcat (fmap (pretty . AsSexp @C) merged)
entry $ bindMatch "dir:state:local:show" $ nil_ $ \sy -> do
@ -842,13 +872,20 @@ syncEntries = do
[StringLike "D"] -> isDir
_ -> const True
state <- getStateFromDir0 False
state <- getStateFromDir0 True
liftIO $ print $ vcat (fmap (pretty . AsSexp @C . snd) (filter (f . snd) state))
entry $ bindMatch "dir:state:remote:show" $ nil_ $ const do
entry $ bindMatch "dir:state:remote:show" $ nil_ $ \syn -> do
let f = case syn of
[StringLike "F"] -> isFile
[StringLike "D"] -> isDir
_ -> const True
dir <- getRunDir
env <- getRunDirEnv dir >>= orThrow DirNotSet
runMaybeT do
@ -858,7 +895,7 @@ syncEntries = do
state <- lift $ getStateFromRefChan rchan
liftIO $ print $ vcat (fmap (pretty . AsSexp @C . snd) state)
liftIO $ print $ vcat (fmap (pretty . AsSexp @C . snd) (filter (f.snd) state))
entry $ bindMatch "dir:config:show" $ nil_ $ const do
@ -869,11 +906,7 @@ syncEntries = do
liftIO $ print $ pretty env
entry $ bindMatch "run" $ nil_ \case
[StringLike what] -> do
runDirectory what
_ -> do
die "command not specified; run hbs2-sync help for details"
_ -> runDirectory
-- debugPrefix :: LoggerEntry -> LoggerEntry