mirror of https://github.com/voidlizard/hbs2
wip
This commit is contained in:
parent
2d91362466
commit
c1b3470949
|
@ -59,6 +59,7 @@ import Control.Monad.Trans.Cont as Exported
|
||||||
import Control.Monad.Trans.Maybe
|
import Control.Monad.Trans.Maybe
|
||||||
import Control.Monad.Except
|
import Control.Monad.Except
|
||||||
import Data.ByteString (ByteString)
|
import Data.ByteString (ByteString)
|
||||||
|
import Data.Ord
|
||||||
import Data.ByteString.Lazy qualified as LBS
|
import Data.ByteString.Lazy qualified as LBS
|
||||||
import Data.Coerce
|
import Data.Coerce
|
||||||
import Data.Either
|
import Data.Either
|
||||||
|
@ -79,8 +80,9 @@ import Data.Time.LocalTime (utcToLocalTime, getCurrentTimeZone, utc)
|
||||||
import Data.Word
|
import Data.Word
|
||||||
import Lens.Micro.Platform
|
import Lens.Micro.Platform
|
||||||
import Streaming.Prelude qualified as S
|
import Streaming.Prelude qualified as S
|
||||||
import System.Directory (getModificationTime,setModificationTime,doesFileExist)
|
import System.Directory (getModificationTime,setModificationTime,doesFileExist,listDirectory)
|
||||||
import System.FilePath.Posix
|
import System.FilePath.Posix
|
||||||
|
import System.FilePattern
|
||||||
import System.Exit qualified as Exit
|
import System.Exit qualified as Exit
|
||||||
import UnliftIO
|
import UnliftIO
|
||||||
|
|
||||||
|
@ -255,6 +257,18 @@ data EntryDesc =
|
||||||
|
|
||||||
newtype AsSexp c a = AsSexp a
|
newtype AsSexp c a = AsSexp a
|
||||||
|
|
||||||
|
pattern TombLikeOpt :: forall {c} . Syntax c
|
||||||
|
pattern TombLikeOpt <- ListVal [StringLike "tomb:", tombLikeValue -> True]
|
||||||
|
|
||||||
|
tombLikeValue :: Syntax c -> Bool
|
||||||
|
tombLikeValue = \case
|
||||||
|
StringLike "#t" -> True
|
||||||
|
StringLike "true" -> True
|
||||||
|
StringLike "yes" -> True
|
||||||
|
StringLike "tomb" -> True
|
||||||
|
LitBoolVal True -> True
|
||||||
|
_ -> False
|
||||||
|
|
||||||
pattern TombEntry :: Entry -> Entry
|
pattern TombEntry :: Entry -> Entry
|
||||||
pattern TombEntry e <- e@(DirEntry (EntryDesc { entryType = Tomb }) _)
|
pattern TombEntry e <- e@(DirEntry (EntryDesc { entryType = Tomb }) _)
|
||||||
|
|
||||||
|
@ -291,8 +305,8 @@ instance IsContext c => ToSexp c Entry where
|
||||||
toSexp (DirEntry w p) = mkForm @c "entry" [toSexp w, mkStr p]
|
toSexp (DirEntry w p) = mkForm @c "entry" [toSexp w, mkStr p]
|
||||||
|
|
||||||
|
|
||||||
makeTomb :: Word64 -> FilePath -> Entry
|
makeTomb :: Word64 -> FilePath -> Maybe HashRef -> Entry
|
||||||
makeTomb t n = DirEntry (EntryDesc Tomb t Nothing) n
|
makeTomb t n h = DirEntry (EntryDesc Tomb t h) n
|
||||||
|
|
||||||
entryPath :: Entry -> FilePath
|
entryPath :: Entry -> FilePath
|
||||||
entryPath (DirEntry _ p) = p
|
entryPath (DirEntry _ p) = p
|
||||||
|
@ -308,6 +322,11 @@ isFile = \case
|
||||||
DirEntry (EntryDesc { entryType = File}) _ -> True
|
DirEntry (EntryDesc { entryType = File}) _ -> True
|
||||||
_ -> False
|
_ -> False
|
||||||
|
|
||||||
|
isTomb :: Entry -> Bool
|
||||||
|
isTomb = \case
|
||||||
|
DirEntry (EntryDesc { entryType = Tomb}) _ -> True
|
||||||
|
_ -> False
|
||||||
|
|
||||||
isDir :: Entry -> Bool
|
isDir :: Entry -> Bool
|
||||||
isDir = \case
|
isDir = \case
|
||||||
DirEntry (EntryDesc { entryType = Dir}) _ -> True
|
DirEntry (EntryDesc { entryType = Dir}) _ -> True
|
||||||
|
@ -370,50 +389,6 @@ runDirectory = do
|
||||||
|
|
||||||
where
|
where
|
||||||
|
|
||||||
postEntryTx refchan path entry = do
|
|
||||||
|
|
||||||
sto <- getStorage
|
|
||||||
|
|
||||||
env <- getRunDirEnv path >>= orThrow DirNotSet
|
|
||||||
|
|
||||||
creds <- view dirSyncCreds env & orThrow DirNotSet
|
|
||||||
|
|
||||||
rch <- Client.getRefChanHead @UNIX refchan
|
|
||||||
>>= orThrow RefChanHeadNotFoundException
|
|
||||||
|
|
||||||
let p = entryPath entry
|
|
||||||
-- 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
|
|
||||||
|
|
||||||
writeEntry path e = do
|
writeEntry path e = do
|
||||||
|
|
||||||
|
@ -497,10 +472,82 @@ runDirectory = do
|
||||||
when (not here || older) do
|
when (not here || older) do
|
||||||
writeEntry path e
|
writeEntry path e
|
||||||
|
|
||||||
|
E (p,TombEntry e) -> do
|
||||||
|
let fullPath = path </> p
|
||||||
|
here <- liftIO $ doesFileExist fullPath
|
||||||
|
when here do
|
||||||
|
notice $ red "tomb entry" <+> pretty (path </> p)
|
||||||
|
rm fullPath
|
||||||
|
|
||||||
E (p,_) -> do
|
E (p,_) -> do
|
||||||
notice $ "skip entry" <+> pretty (path </> p)
|
notice $ "skip entry" <+> pretty (path </> p)
|
||||||
|
|
||||||
|
|
||||||
|
postEntryTx :: ( MonadUnliftIO m
|
||||||
|
, HasStorage m
|
||||||
|
, HasRunDir m
|
||||||
|
, HasClientAPI StorageAPI UNIX m
|
||||||
|
, HasClientAPI RefChanAPI UNIX m
|
||||||
|
)
|
||||||
|
=> MyRefChan
|
||||||
|
-> FilePath
|
||||||
|
-> Entry
|
||||||
|
-> m ()
|
||||||
|
postEntryTx refchan path entry = do
|
||||||
|
|
||||||
|
sto <- getStorage
|
||||||
|
|
||||||
|
env <- getRunDirEnv path >>= orThrow DirNotSet
|
||||||
|
|
||||||
|
creds <- view dirSyncCreds env & orThrow DirNotSet
|
||||||
|
|
||||||
|
rch <- Client.getRefChanHead @UNIX refchan
|
||||||
|
>>= orThrow RefChanHeadNotFoundException
|
||||||
|
|
||||||
|
void $ runMaybeT do
|
||||||
|
|
||||||
|
guard (isFile entry || isTomb entry)
|
||||||
|
|
||||||
|
let p = entryPath entry
|
||||||
|
-- FIXME: dangerous!
|
||||||
|
lbs <- if isTomb entry then do
|
||||||
|
pure ""
|
||||||
|
else
|
||||||
|
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)
|
||||||
|
<> if not (isTomb entry) then HM.empty
|
||||||
|
else HM.singleton "tomb" "#t"
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
lift $ postRefChanTx @UNIX refchan box
|
||||||
|
|
||||||
|
|
||||||
merge :: Entry -> Entry -> Entry
|
merge :: Entry -> Entry -> Entry
|
||||||
merge a b = do
|
merge a b = do
|
||||||
if | getEntryTimestamp a > getEntryTimestamp b -> a
|
if | getEntryTimestamp a > getEntryTimestamp b -> a
|
||||||
|
@ -549,18 +596,30 @@ mergeState orig = do
|
||||||
|
|
||||||
let files = [ (p,e) | (p,e) <- orig, isFile e ] & Map.fromListWith merge
|
let files = [ (p,e) | (p,e) <- orig, isFile e ] & Map.fromListWith merge
|
||||||
|
|
||||||
|
let tombs = [ (p,e) | (p,e) <- orig, isTomb e ] & Map.fromListWith merge
|
||||||
|
|
||||||
let names = Map.keysSet (dirs <> files)
|
let names = Map.keysSet (dirs <> files)
|
||||||
|
|
||||||
now <- liftIO $ getPOSIXTime <&> round
|
now <- liftIO $ getPOSIXTime <&> round
|
||||||
|
|
||||||
S.toList_ do
|
S.toList_ do
|
||||||
for_ (Map.toList files) $ \(p,e@(DirEntry d _)) -> do
|
for_ (Map.toList files) $ \(p,e@(DirEntry d _)) -> do
|
||||||
if Map.member p dirs then do
|
if
|
||||||
let new = uniqName names p
|
| Map.member p dirs -> do
|
||||||
S.yield $ M (p, new, DirEntry d new)
|
let new = uniqName names p
|
||||||
S.yield $ N (p, makeTomb now p)
|
S.yield $ M (p, new, DirEntry d new)
|
||||||
else
|
S.yield $ N (p, makeTomb now p Nothing)
|
||||||
S.yield $ E (p,e)
|
|
||||||
|
| Map.member p tombs -> do
|
||||||
|
let tomb = Map.lookup p tombs
|
||||||
|
case tomb of
|
||||||
|
Just t | getEntryTimestamp t >= getEntryTimestamp e -> do
|
||||||
|
S.yield $ E (p,t)
|
||||||
|
|
||||||
|
_ -> S.yield $ E (p,e)
|
||||||
|
|
||||||
|
| otherwise -> do
|
||||||
|
S.yield $ E (p,e)
|
||||||
|
|
||||||
where
|
where
|
||||||
uniqName names0 name = do
|
uniqName names0 name = do
|
||||||
|
@ -696,10 +755,19 @@ getStateFromRefChan rchan = do
|
||||||
let loc = headDef "" [ l | ListVal [StringLike "location:", StringLike l] <- what ]
|
let loc = headDef "" [ l | ListVal [StringLike "location:", StringLike l] <- what ]
|
||||||
|
|
||||||
void $ runMaybeT do
|
void $ runMaybeT do
|
||||||
fn <- toMPlus $ headMay [ l | ListVal [StringLike "file-name:", StringLike l] <- what ]
|
fn <- toMPlus $ headMay [ l | ListVal [StringLike "file-name:", StringLike l] <- what ]
|
||||||
ts <- toMPlus $ HM.lookup txh tsmap
|
ts <- toMPlus $ HM.lookup txh tsmap
|
||||||
let r = entriesFromFile (Just tree) ts (loc </> fn)
|
let tomb = or [ True | TombLikeOpt <- what ]
|
||||||
lift $ S.yield r
|
let fullPath = loc </> fn
|
||||||
|
|
||||||
|
debug $ red $ "META" <+> pretty what
|
||||||
|
|
||||||
|
if tomb then do
|
||||||
|
lift $ S.yield $
|
||||||
|
Map.singleton fullPath (makeTomb ts fullPath (Just tree))
|
||||||
|
else do
|
||||||
|
let r = entriesFromFile (Just tree) ts fullPath
|
||||||
|
lift $ S.yield r
|
||||||
|
|
||||||
pure $ Map.toList $ Map.unionsWith merge ess0
|
pure $ Map.toList $ Map.unionsWith merge ess0
|
||||||
|
|
||||||
|
@ -909,6 +977,63 @@ syncEntries = do
|
||||||
_ -> runDirectory
|
_ -> runDirectory
|
||||||
|
|
||||||
|
|
||||||
|
entry $ bindMatch "prune" $ nil_ \case
|
||||||
|
[] -> do
|
||||||
|
|
||||||
|
|
||||||
|
path <- getRunDir
|
||||||
|
|
||||||
|
env <- getRunDirEnv path >>= orThrow DirNotSet
|
||||||
|
|
||||||
|
let excl = view dirSyncExclude env
|
||||||
|
|
||||||
|
let skip p = or [ i ?== p | i <- excl ]
|
||||||
|
|
||||||
|
dirs <- S.toList_ do
|
||||||
|
flip fix [path] $ \next -> \case
|
||||||
|
(d:ds) -> do
|
||||||
|
dirs <- liftIO (listDirectory d)
|
||||||
|
let es = [ path </> d </> x | x <- dirs, not (skip x) ]
|
||||||
|
dd <- liftIO $ filterM doesDirectoryExist es
|
||||||
|
S.each dd
|
||||||
|
next (ds <> dd)
|
||||||
|
|
||||||
|
[] -> pure ()
|
||||||
|
|
||||||
|
for_ (L.sortBy (comparing Down) dirs) $ \d -> do
|
||||||
|
pu <- liftIO (listDirectory d) <&> L.null
|
||||||
|
when pu do
|
||||||
|
notice $ red "prune" <+> pretty d
|
||||||
|
rm d
|
||||||
|
|
||||||
|
_ -> pure ()
|
||||||
|
|
||||||
|
|
||||||
|
entry $ bindMatch "tomb" $ nil_ \case
|
||||||
|
[StringLike p] -> do
|
||||||
|
|
||||||
|
path <- getRunDir
|
||||||
|
env <- getRunDirEnv path >>= orThrow DirNotSet
|
||||||
|
|
||||||
|
void $ runMaybeT do
|
||||||
|
|
||||||
|
let fullPath = path </> p
|
||||||
|
|
||||||
|
rchan <- view dirSyncRefChan env
|
||||||
|
& toMPlus
|
||||||
|
|
||||||
|
|
||||||
|
here <- liftIO (doesFileExist fullPath)
|
||||||
|
guard here
|
||||||
|
|
||||||
|
now <- liftIO getPOSIXTime <&> round
|
||||||
|
|
||||||
|
notice $ red "ABOUT TO POST TOMB TX" <+> pretty p
|
||||||
|
lift $ postEntryTx rchan path (makeTomb now p mzero)
|
||||||
|
|
||||||
|
_ -> pure ()
|
||||||
|
|
||||||
|
|
||||||
-- debugPrefix :: LoggerEntry -> LoggerEntry
|
-- debugPrefix :: LoggerEntry -> LoggerEntry
|
||||||
debugPrefix = toStderr . logPrefix "[debug] "
|
debugPrefix = toStderr . logPrefix "[debug] "
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue