diff options
| author | Roman Smrž <roman.smrz@seznam.cz> | 2020-06-17 22:30:47 +0200 | 
|---|---|---|
| committer | Roman Smrž <roman.smrz@seznam.cz> | 2020-06-17 22:30:47 +0200 | 
| commit | a4437f0479a721aeebac305e403b88b18a5f7d5f (patch) | |
| tree | 075e7db76a5a0c2021dec61a8bad2620ad01fd08 | |
| parent | b08e5a3e6d82ca5e5a2e29e791a2e61bf08964a4 (diff) | |
Storage: typed heads
| -rw-r--r-- | src/Attach.hs | 37 | ||||
| -rw-r--r-- | src/Main.hs | 35 | ||||
| -rw-r--r-- | src/Message.hs | 9 | ||||
| -rw-r--r-- | src/Network.hs | 45 | ||||
| -rw-r--r-- | src/Service.hs | 14 | ||||
| -rw-r--r-- | src/State.hs | 132 | ||||
| -rw-r--r-- | src/Storage.hs | 188 | ||||
| -rw-r--r-- | src/Storage/Internal.hs | 15 | 
8 files changed, 264 insertions, 211 deletions
| diff --git a/src/Attach.hs b/src/Attach.hs index 761da0f..95f0a67 100644 --- a/src/Attach.hs +++ b/src/Attach.hs @@ -111,8 +111,7 @@ instance Service AttachService where              verifyAttachedIdentity sdata >>= \case                  Just identity -> do                      svcPrint $ "Accepted updated identity" -                    st <- storedStorage <$> svcGetLocal -                    finalizeAttach st identity keys +                    svcSetLocal =<< finalizeAttach identity keys =<< svcGetLocal                  Nothing -> do                      svcPrint $ "Failed to verify new identity"                      svcSet $ AttachFailed @@ -151,9 +150,10 @@ attachToOwner _ self peer = do          NoAttach -> return (Just $ AttachRequest (nonceDigest self pid nonce BA.empty), OurRequest nonce)          _ -> throwError "alredy in progress" -attachAccept :: (MonadIO m, MonadError String m) => (String -> IO ()) -> UnifiedIdentity -> Peer -> m () -attachAccept printMsg self peer = do -    let st = storedStorage $ idData self +attachAccept :: (MonadIO m, MonadError String m) => (String -> IO ()) -> Head LocalState -> Peer -> m () +attachAccept printMsg h peer = do +    let st = refStorage $ headRef h +        self = headLocalIdentity h      sendToPeerWith self peer $ \case          NoAttach -> throwError $ "none in progress"          OurRequest {} -> throwError $ "waiting for peer" @@ -161,14 +161,15 @@ attachAccept printMsg self peer = do              liftIO $ printMsg $ "Confirmed peer, waiting for updated identity"              return (Nothing, OurRequestReady)          OurRequestConfirm (Just (identity, keys)) -> do -            liftIO $ printMsg $ "Accepted updated identity" -            finalizeAttach st identity keys +            liftIO $ do +                printMsg $ "Accepted updated identity" +                updateLocalState_ h $ finalizeAttach identity keys              return (Nothing, AttachDone)          OurRequestReady -> throwError $ "alredy accepted, waiting for peer"          PeerRequest {} -> throwError $ "waiting for peer"          PeerRequestConfirm -> do              liftIO $ printMsg $ "Accepted new attached device, seding updated identity" -            owner <- liftIO $ mergeSharedIdentity st +            owner <- liftIO $ mergeSharedIdentity h              PeerIdentityFull pid <- return $ peerIdentity peer              Just secret <- liftIO $ loadKey $ idKeyIdentity owner              liftIO $ do @@ -209,15 +210,15 @@ verifyAttachedIdentity sdata = do          return identity -finalizeAttach :: MonadIO m => Storage -> UnifiedIdentity -> [ScrubbedBytes] -> m () -finalizeAttach st identity skeys = do -    liftIO $ updateLocalState_ st $ \slocal -> do -        let owner = finalOwner identity -        pkeys <- mapM (copyStored st) [ idKeyIdentity owner, idKeyMessage owner ] -        mapM_ storeKey $ catMaybes [ keyFromData sec pub | sec <- skeys, pub <- pkeys ] +finalizeAttach :: MonadIO m => UnifiedIdentity -> [ScrubbedBytes] -> Stored LocalState -> m (Stored LocalState) +finalizeAttach identity skeys slocal = liftIO $ do +    let owner = finalOwner identity +        st = storedStorage slocal +    pkeys <- mapM (copyStored st) [ idKeyIdentity owner, idKeyMessage owner ] +    mapM_ storeKey $ catMaybes [ keyFromData sec pub | sec <- skeys, pub <- pkeys ] -        shared <- makeSharedStateUpdate st (idDataF owner) (lsShared $ fromStored slocal) -        wrappedStore st (fromStored slocal) -            { lsIdentity = idData identity -            , lsShared = [ shared ] +    shared <- makeSharedStateUpdate st (idDataF owner) (lsShared $ fromStored slocal) +    wrappedStore st (fromStored slocal) +        { lsIdentity = idData identity +        , lsShared = [ shared ]              } diff --git a/src/Main.hs b/src/Main.hs index 34c2b3b..c961f4f 100644 --- a/src/Main.hs +++ b/src/Main.hs @@ -74,7 +74,7 @@ main = do              Nothing -> error "ref does not exist"              Just ref -> print $ storedGeneration (wrappedLoad ref :: Stored Object) -        ["update-identity"] -> updateSharedIdentity st +        ["update-identity"] -> updateSharedIdentity =<< loadLocalStateHead st          ("update-identity" : srefs) -> do              sequence <$> mapM (readRef st . BC.pack) srefs >>= \case @@ -89,15 +89,14 @@ main = do  interactiveLoop :: Storage -> String -> IO ()  interactiveLoop st bhost = runInputT defaultSettings $ do -    origIdentity <- liftIO $ loadLocalIdentity st -    outputStrLn $ T.unpack $ displayIdentity origIdentity +    erebosHead <- liftIO $ loadLocalStateHead st +    outputStrLn $ T.unpack $ displayIdentity $ headLocalIdentity erebosHead      haveTerminalUI >>= \case True -> return ()                               False -> error "Requires terminal"      extPrint <- getExternalPrint      let extPrintLn str = extPrint $ str ++ "\n";      server <- liftIO $ do -        erebosHead <- loadLocalStateHead st          startServer erebosHead extPrintLn bhost              [ SomeService @AttachService Proxy              , SomeService @SyncService Proxy @@ -139,9 +138,12 @@ interactiveLoop st bhost = runInputT defaultSettings $ do                                         then (cmdSetPeer $ read scmd, args)                                         else (fromMaybe (cmdUnknown scmd) $ lookup scmd commands, args)                      _        -> (cmdSend, input) -            curIdentity <- liftIO $ loadLocalIdentity st +            h <- liftIO (reloadHead erebosHead) >>= \case +                Just h  -> return h +                Nothing -> do lift $ lift $ extPrint "current head deleted" +                              mzero              res <- liftIO $ runExceptT $ flip execStateT cstate $ runReaderT cmd CommandInput -                { ciSelf = curIdentity +                { ciHead = h                  , ciServer = server                  , ciLine = line                  , ciPrint = extPrintLn @@ -158,7 +160,7 @@ interactiveLoop st bhost = runInputT defaultSettings $ do  data CommandInput = CommandInput -    { ciSelf :: UnifiedIdentity +    { ciHead :: Head LocalState      , ciServer :: Server      , ciLine :: String      , ciPrint :: String -> IO () @@ -215,41 +217,38 @@ cmdSetPeer n | n < 1     = liftIO $ putStrLn "Invalid peer index"  cmdSend :: Command  cmdSend = void $ do -    self <- asks ciSelf +    ehead <- asks ciHead      Just peer <- gets csPeer      text <- asks ciLine -    smsg <- sendDirectMessage self peer $ T.pack text +    smsg <- sendDirectMessage ehead peer $ T.pack text      tzone <- liftIO $ getCurrentTimeZone      liftIO $ putStrLn $ formatMessage tzone $ fromStored smsg  cmdHistory :: Command  cmdHistory = void $ do -    self <- asks ciSelf -    let st = storedStorage $ idData self +    ehead <- asks ciHead      Just peer <- gets csPeer      PeerIdentityFull pid <- return $ peerIdentity peer      let powner = finalOwner pid -    Just erebosHead <- liftIO $ loadHead st "erebos" -    let erebos = wrappedLoad (headRef erebosHead)      Just thread <- return $ find (sameIdentity powner . msgPeer) $ -        messageThreadView $ lookupSharedValue $ lsShared $ fromStored erebos +        messageThreadView $ lookupSharedValue $ lsShared $ headObject ehead      tzone <- liftIO $ getCurrentTimeZone      liftIO $ mapM_ (putStrLn . formatMessage tzone) $ reverse $ take 50 $ threadToList thread  cmdUpdateIdentity :: Command  cmdUpdateIdentity = void $ do -    st <- asks $ storedStorage . idData . ciSelf -    liftIO $ updateSharedIdentity st +    ehead <- asks ciHead +    liftIO $ updateSharedIdentity ehead  cmdAttach :: Command  cmdAttach = join $ attachToOwner      <$> asks ciPrint -    <*> asks ciSelf +    <*> asks (headLocalIdentity . ciHead)      <*> (maybe (throwError "no peer selected") return =<< gets csPeer)  cmdAttachAccept :: Command  cmdAttachAccept = join $ attachAccept      <$> asks ciPrint -    <*> asks ciSelf +    <*> asks ciHead      <*> (maybe (throwError "no peer selected") return =<< gets csPeer) diff --git a/src/Message.hs b/src/Message.hs index 0039d7e..874e375 100644 --- a/src/Message.hs +++ b/src/Message.hs @@ -113,14 +113,15 @@ findMsgProperty pid sel mss = concat $ flip findProperty mss $ \x -> do      return $ sel x -sendDirectMessage :: (MonadIO m, MonadError String m) => UnifiedIdentity -> Peer -> Text -> m (Stored DirectMessage) -sendDirectMessage self peer text = do +sendDirectMessage :: (MonadIO m, MonadError String m) => Head LocalState -> Peer -> Text -> m (Stored DirectMessage) +sendDirectMessage h peer text = do      pid <- case peerIdentity peer of PeerIdentityFull pid -> return pid                                       _ -> throwError "incomplete peer identity" -    let st = storedStorage $ idData self +    let st = refStorage $ headRef h +        self = headLocalIdentity h          powner = finalOwner pid -    smsg <- liftIO $ updateSharedState st $ \prev -> do +    smsg <- liftIO $ updateSharedState h $ \prev -> do          let sent = findMsgProperty powner msSent prev              received = findMsgProperty powner msReceived prev diff --git a/src/Network.hs b/src/Network.hs index f07e7ce..5685627 100644 --- a/src/Network.hs +++ b/src/Network.hs @@ -181,7 +181,7 @@ receivedWaitingRef nref wr@(WaitingRef _ _ mvar) = do      checkWaitingRef wr -startServer :: Head -> (String -> IO ()) -> String -> [SomeService] -> IO Server +startServer :: Head LocalState -> (String -> IO ()) -> String -> [SomeService] -> IO Server  startServer origHead logd bhost services = do      let storage = refStorage $ headRef origHead      chanPeer <- newChan @@ -271,7 +271,7 @@ startServer origHead logd bhost services = do                                     forM_ objs $ storeObject $ peerInStorage peer                                     identity <- readMVar midentity                                     let svcs = map someServiceID services -                                   handlePacket logd identity secure peer chanSvc svcs header >>= \case +                                   handlePacket logd origHead identity secure peer chanSvc svcs header >>= \case                                         Just peer' -> do                                             writeChan chanPeer peer'                                             return $ M.insert paddr peer' pvalue @@ -307,13 +307,18 @@ startServer origHead logd bhost services = do                                      { svcPeer = peerId                                      , svcPrintOp = logd                                      } -                            (rsp, (s', gs')) <- handleServicePacket storage inp s gs (wrappedLoad ref :: Stored s) -                            identity <- readMVar midentity -                            runExceptT (sendToPeerList identity peer rsp) >>= \case -                                Left err -> logd $ "failed to send response to peer: " ++ show err -                                Right () -> return () -                            return (M.insert svc (SomeServiceState proxy s') svcs, -                                M.insert svc (SomeServiceGlobalState proxy gs') global) +                            reloadHead origHead >>= \case +                                Nothing -> do +                                    logd $ "current head deleted" +                                    return (svcs, global) +                                Just h -> do +                                    (rsp, (s', gs')) <- handleServicePacket h inp s gs (wrappedLoad ref :: Stored s) +                                    identity <- readMVar midentity +                                    runExceptT (sendToPeerList identity peer rsp) >>= \case +                                        Left err -> logd $ "failed to send response to peer: " ++ show err +                                        Right () -> return () +                                    return (M.insert svc (SomeServiceState proxy s') svcs, +                                        M.insert svc (SomeServiceGlobalState proxy gs') global)                          _ -> do                              logd $ "unhandled service '" ++ show (toUUID svc) ++ "'"                              return (svcs, global) @@ -352,10 +357,10 @@ appendDistinct x (y:ys) | x == y    = y : ys                          | otherwise = y : appendDistinct x ys  appendDistinct x [] = [x] -handlePacket :: (String -> IO ()) -> UnifiedIdentity -> Bool +handlePacket :: (String -> IO ()) -> Head LocalState -> UnifiedIdentity -> Bool      -> Peer -> Chan (Peer, ServiceID, Ref) -> [ServiceID]      -> TransportHeader -> IO (Maybe Peer) -handlePacket logd identity secure opeer chanSvc svcs (TransportHeader headers) = do +handlePacket logd origHead identity secure opeer chanSvc svcs (TransportHeader headers) = do      let sidentity = idData identity          DatagramAddress paddr = peerAddress opeer          plaintextRefs = map (refDigest . storedRef) $ concatMap (collectStoredObjects . wrappedLoad) $ concat @@ -373,7 +378,7 @@ handlePacket logd identity secure opeer chanSvc svcs (TransportHeader headers) =                  gets (peerChannel . phPeer) >>= \case                      ChannelOurAccept acc ch | refDigest (storedRef acc) == refDigest ref -> do                          updatePeer $ \p -> p { peerChannel = ChannelEstablished (fromStored ch) } -                        finalizedChannel identity +                        finalizedChannel origHead identity                      _ -> return ()              Rejected _ -> return () @@ -442,7 +447,7 @@ handlePacket logd identity secure opeer chanSvc svcs (TransportHeader headers) =              TrChannelAccept accref -> do                  let process = do                          addHeader $ Acknowledged accref -                        handleChannelAccept identity accref +                        handleChannelAccept origHead identity accref                  gets (peerChannel . phPeer) >>= \case                      ChannelWait {} -> process                      ChannelOurRequest {} -> process @@ -550,8 +555,8 @@ handleChannelRequest identity reqref = do          Nothing -> do              updatePeer $ \p -> p { peerChannel = ChannelPeerRequest reqref } -handleChannelAccept :: UnifiedIdentity -> PartialRef -> PacketHandler () -handleChannelAccept identity accref = do +handleChannelAccept :: Head LocalState -> UnifiedIdentity -> PartialRef -> PacketHandler () +handleChannelAccept oh identity accref = do      pst <- gets $ peerStorage . phPeer      copyRef pst accref >>= \case          Right acc -> do @@ -570,12 +575,12 @@ handleChannelAccept identity accref = do                  { peerIdentity = PeerIdentityFull pid                  , peerChannel = ChannelEstablished $ fromStored ch                  } -            finalizedChannel identity +            finalizedChannel oh identity          Left dgst -> throwError $ "missing accept data " ++ BC.unpack (showRefDigest dgst) -finalizedChannel :: UnifiedIdentity -> PacketHandler () -finalizedChannel self = do +finalizedChannel :: Head LocalState -> UnifiedIdentity -> PacketHandler () +finalizedChannel oh self = do      -- Identity update      ist <- gets $ peerInStorage . phPeer      addHeader $ AnnounceSelf $ partialRef ist $ storedRef $ idData $ self @@ -585,8 +590,8 @@ finalizedChannel self = do      gets phPeer >>= \case          peer | PeerIdentityFull pid <- peerIdentity peer               , finalOwner pid `sameIdentity` finalOwner self -> do -                 shared <- lsShared . fromStored <$> -                     liftIO (loadLocalState $ storedStorage $ idData self) +                 Just h <- liftIO $ reloadHead oh +                 let shared = lsShared $ headObject h                   addHeader $ ServiceType $ serviceID @SyncService Proxy                   mapM_ (addHeader . ServiceRef . partialRef ist . storedRef) shared                   mapM_ (addBody . storedRef) shared diff --git a/src/Service.hs b/src/Service.hs index b5106ce..704bc67 100644 --- a/src/Service.hs +++ b/src/Service.hs @@ -91,11 +91,9 @@ data ServiceHandlerState s = ServiceHandlerState  newtype ServiceHandler s a = ServiceHandler (ReaderT ServiceInput (WriterT [ServiceReply s] (StateT (ServiceHandlerState s) (ExceptT String IO))) a)      deriving (Functor, Applicative, Monad, MonadReader ServiceInput, MonadWriter [ServiceReply s], MonadState (ServiceHandlerState s), MonadError String, MonadIO) -handleServicePacket :: Service s => Storage -> ServiceInput -> ServiceState s -> ServiceGlobalState s -> Stored s -> IO ([ServiceReply s], (ServiceState s, ServiceGlobalState s)) -handleServicePacket st input svc global packet = do -    herb <- loadLocalStateHead st -    let erb = wrappedLoad $ headRef herb -        sstate = ServiceHandlerState { svcValue = svc, svcGlobal = global, svcLocal = erb } +handleServicePacket :: Service s => Head LocalState -> ServiceInput -> ServiceState s -> ServiceGlobalState s -> Stored s -> IO ([ServiceReply s], (ServiceState s, ServiceGlobalState s)) +handleServicePacket h input svc global packet = do +    let sstate = ServiceHandlerState { svcValue = svc, svcGlobal = global, svcLocal = headStoredObject h }          ServiceHandler handler = serviceHandler packet      (runExceptT $ flip runStateT sstate $ execWriterT $ flip runReaderT input $ handler) >>= \case          Left err -> do @@ -103,9 +101,9 @@ handleServicePacket st input svc global packet = do              return ([], (svc, global))          Right (rsp, sstate')              | svcLocal sstate' == svcLocal sstate -> return (rsp, (svcValue sstate', svcGlobal sstate')) -            | otherwise -> replaceHead (svcLocal sstate') (Right herb) >>= \case -                Left  _ -> handleServicePacket st input svc global packet -                Right _ -> return (rsp, (svcValue sstate', svcGlobal sstate')) +            | otherwise -> replaceHead h (svcLocal sstate') >>= \case +                Left (Just h') -> handleServicePacket h' input svc global packet +                _              -> return (rsp, (svcValue sstate', svcGlobal sstate'))  svcGet :: ServiceHandler s (ServiceState s)  svcGet = gets svcValue diff --git a/src/State.hs b/src/State.hs index 15ae7d2..8e9e320 100644 --- a/src/State.hs +++ b/src/State.hs @@ -3,21 +3,19 @@ module State (      SharedState, SharedType(..),      SharedTypeID, mkSharedTypeID, -    loadLocalState, loadLocalStateHead, +    loadLocalStateHead,      updateLocalState, updateLocalState_,      updateSharedState, updateSharedState_,      lookupSharedValue, makeSharedStateUpdate, -    loadLocalIdentity, headLocalIdentity, +    headLocalIdentity,      mergeSharedIdentity,      updateSharedIdentity,      interactiveIdentityUpdate,  ) where -import Control.Monad -  import Data.Foldable  import Data.Maybe  import qualified Data.Text as T @@ -62,6 +60,9 @@ instance Storable LocalState where          <$> loadRef "id"          <*> loadRefs "shared" +instance HeadType LocalState where +    headTypeID _ = mkHeadTypeID "1d7491a9-7bcb-4eaa-8f13-c8c4c4087e4e" +  instance Storable SharedState where      store' st = storeRec $ do          mapM_ (storeRef "PREV") $ ssPrev st @@ -77,80 +78,69 @@ instance SharedType (Signed IdentityData) where      sharedTypeID _ = mkSharedTypeID "0c6c1fe0-f2d7-4891-926b-c332449f7871" -loadLocalState :: Storage -> IO (Stored LocalState) -loadLocalState = return . wrappedLoad . headRef <=< loadLocalStateHead - -loadLocalStateHead :: Storage -> IO Head -loadLocalStateHead st = loadHeadDef st "erebos" $ do -    putStr "Name: " -    hFlush stdout -    name <- T.getLine - -    putStr "Device: " -    hFlush stdout -    devName <- T.getLine - -    (owner, secret) <- if -        | T.null name -> return (Nothing, Nothing) -        | otherwise -> do -            (secret, public) <- generateKeys st -            (_secretMsg, publicMsg) <- generateKeys st - -            return . (, Just secret) . Just =<< wrappedStore st =<< sign secret =<< -                wrappedStore st (emptyIdentityData public) -                { iddName = Just name, iddKeyMessage = Just publicMsg } - -    (devSecret, devPublic) <- generateKeys st -    (_devSecretMsg, devPublicMsg) <- generateKeys st - -    identity <- wrappedStore st =<< maybe return signAdd secret =<< sign devSecret =<< wrappedStore st (emptyIdentityData devPublic) -        { iddName = if T.null devName then Nothing else Just devName -        , iddOwner = owner -        , iddKeyMessage = Just devPublicMsg -        } - -    shared <- wrappedStore st $ SharedState -        { ssPrev = [] -        , ssType = Just $ sharedTypeID @(Signed IdentityData) Proxy -        , ssValue = [storedRef $ fromMaybe identity owner] -        } -    return $ LocalState -        { lsIdentity = identity -        , lsShared = [shared] -        } - -loadLocalIdentity :: Storage -> IO UnifiedIdentity -loadLocalIdentity = return . headLocalIdentity <=< loadLocalStateHead - -headLocalIdentity :: Head -> UnifiedIdentity +loadLocalStateHead :: Storage -> IO (Head LocalState) +loadLocalStateHead st = loadHeads st >>= \case +    (h:_) -> return h +    [] -> do +        putStr "Name: " +        hFlush stdout +        name <- T.getLine + +        putStr "Device: " +        hFlush stdout +        devName <- T.getLine + +        (owner, secret) <- if +            | T.null name -> return (Nothing, Nothing) +            | otherwise -> do +                (secret, public) <- generateKeys st +                (_secretMsg, publicMsg) <- generateKeys st + +                return . (, Just secret) . Just =<< wrappedStore st =<< sign secret =<< +                    wrappedStore st (emptyIdentityData public) +                    { iddName = Just name, iddKeyMessage = Just publicMsg } + +        (devSecret, devPublic) <- generateKeys st +        (_devSecretMsg, devPublicMsg) <- generateKeys st + +        identity <- wrappedStore st =<< maybe return signAdd secret =<< sign devSecret =<< wrappedStore st (emptyIdentityData devPublic) +            { iddName = if T.null devName then Nothing else Just devName +            , iddOwner = owner +            , iddKeyMessage = Just devPublicMsg +            } + +        shared <- wrappedStore st $ SharedState +            { ssPrev = [] +            , ssType = Just $ sharedTypeID @(Signed IdentityData) Proxy +            , ssValue = [storedRef $ fromMaybe identity owner] +            } +        storeHead st $ LocalState +            { lsIdentity = identity +            , lsShared = [shared] +            } + +headLocalIdentity :: Head LocalState -> UnifiedIdentity  headLocalIdentity h = -    let ls = load $ headRef h +    let ls = headObject h       in maybe (error "failed to verify local identity")              (updateOwners (lookupSharedValue $ lsShared ls))              (validateIdentity $ lsIdentity ls) -updateLocalState_ :: Storage -> (Stored LocalState -> IO (Stored LocalState)) -> IO () -updateLocalState_ st f = updateLocalState st (fmap (,()) . f) - -updateLocalState :: Storage -> (Stored LocalState -> IO (Stored LocalState, a)) -> IO a -updateLocalState st f = do -    Just erebosHead <- loadHead st "erebos" -    let ls = wrappedLoad (headRef erebosHead) -    (ls', x) <- f ls -    when (ls' /= ls) $ do -        Right _ <- replaceHead ls' (Right erebosHead) -        return () -    return x +updateLocalState_ :: Head LocalState -> (Stored LocalState -> IO (Stored LocalState)) -> IO () +updateLocalState_ h f = updateLocalState h (fmap (,()) . f) +updateLocalState :: Head LocalState -> (Stored LocalState -> IO (Stored LocalState, a)) -> IO a +updateLocalState h f = snd <$> updateHead h f -updateSharedState_ :: SharedType a => Storage -> ([Stored a] -> IO ([Stored a])) -> IO () -updateSharedState_ st f = updateSharedState st (fmap (,()) . f) +updateSharedState_ :: SharedType a => Head LocalState -> ([Stored a] -> IO ([Stored a])) -> IO () +updateSharedState_ h f = updateSharedState h (fmap (,()) . f) -updateSharedState :: forall a b. SharedType a => Storage -> ([Stored a] -> IO ([Stored a], b)) -> IO b -updateSharedState st f = updateLocalState st $ \ls -> do +updateSharedState :: forall a b. SharedType a => Head LocalState -> ([Stored a] -> IO ([Stored a], b)) -> IO b +updateSharedState h f = updateLocalState h $ \ls -> do      let shared = lsShared $ fromStored ls          val = lookupSharedValue shared +        st = refStorage $ headRef h      (val', x) <- f val      (,x) <$> if val' == val                  then return ls @@ -171,14 +161,14 @@ makeSharedStateUpdate st val prev = wrappedStore st SharedState      } -mergeSharedIdentity :: Storage -> IO UnifiedIdentity -mergeSharedIdentity st = updateSharedState st $ \sdata -> do +mergeSharedIdentity :: Head LocalState -> IO UnifiedIdentity +mergeSharedIdentity = flip updateSharedState $ \sdata -> do      let Just cidentity = validateIdentityF sdata      identity <- mergeIdentity cidentity      return ([idData identity], identity) -updateSharedIdentity :: Storage -> IO () -updateSharedIdentity st = updateSharedState_ st $ \sdata -> do +updateSharedIdentity :: Head LocalState -> IO () +updateSharedIdentity = flip updateSharedState_ $ \sdata -> do      let Just identity = validateIdentityF sdata      (:[]) . idData <$> interactiveIdentityUpdate identity diff --git a/src/Storage.hs b/src/Storage.hs index 5a5d992..92a1e1f 100644 --- a/src/Storage.hs +++ b/src/Storage.hs @@ -16,9 +16,11 @@ module Storage (      storeObject,      collectObjects, collectStoredObjects, -    Head, -    headName, headRef, headObject, -    loadHeads, loadHead, loadHeadDef, replaceHead, +    Head, HeadType(..), +    HeadTypeID, mkHeadTypeID, +    headId, headRef, headObject, headStoredObject, +    loadHeads, loadHead, reloadHead, +    storeHead, replaceHead, updateHead, updateHead_,      watchHead,      Storable(..), ZeroStorable(..), @@ -88,10 +90,13 @@ import Data.Time.Calendar  import Data.Time.Clock  import Data.Time.Format  import Data.Time.LocalTime +import Data.Typeable  import Data.UUID (UUID)  import qualified Data.UUID as U +import qualified Data.UUID.V4 as U  import System.Directory +import System.FilePath  import System.INotify  import System.IO.Error  import System.IO.Unsafe @@ -106,7 +111,7 @@ openStorage :: FilePath -> IO Storage  openStorage path = do      createDirectoryIfMissing True $ path ++ "/objects"      createDirectoryIfMissing True $ path ++ "/heads" -    watchers <- newMVar (Nothing, []) +    watchers <- newMVar ([], [])      refgen <- newMVar =<< HT.new      return $ Storage { stBacking = StorageDir path watchers, stParent = Nothing, stRefGeneration = refgen } @@ -357,90 +362,137 @@ collectOtherStored seen _ = ([], seen)  type Head = Head' Complete -headName :: Head -> String -headName (Head name _) = name +headId :: Head a -> HeadID +headId (Head uuid _) = uuid -headRef :: Head -> Ref -headRef (Head _ ref) = ref +headRef :: Head a -> Ref +headRef (Head _ sx) = storedRef sx -headObject :: Storable a => Head -> a -headObject = load . headRef +headObject :: Head a -> a +headObject (Head _ sx) = fromStored sx +headStoredObject :: Head a -> Stored a +headStoredObject (Head _ sx) = sx + +deriving instance StorableUUID HeadID +deriving instance StorableUUID HeadTypeID + +mkHeadTypeID :: String -> HeadTypeID +mkHeadTypeID = maybe (error "Invalid head type ID") HeadTypeID . U.fromString + +class Storable a => HeadType a where +    headTypeID :: proxy a -> HeadTypeID -loadHeads :: Storage -> IO [Head] -loadHeads s@(Storage { stBacking = StorageDir { dirPath = spath }}) = do -    let hpath = spath ++ "/heads/" -    files <- filterM (doesFileExist . (hpath++)) =<< getDirectoryContents hpath -    forM files $ \hname -> do -        (h:_) <- BC.lines <$> B.readFile (hpath ++ "/" ++ hname) -        Just ref <- readRef s h -        return $ Head hname ref -loadHeads Storage { stBacking = StorageMemory { memHeads = theads } } = readMVar theads -loadHead :: Storage -> String -> IO (Maybe Head) -loadHead s@(Storage { stBacking = StorageDir { dirPath = spath }}) hname = do +headTypePath :: FilePath -> HeadTypeID -> FilePath +headTypePath spath (HeadTypeID tid) = spath </> "heads" </> U.toString tid + +headPath :: FilePath -> HeadTypeID -> HeadID -> FilePath +headPath spath tid (HeadID hid) = headTypePath spath tid </> U.toString hid + +loadHeads :: forall a. HeadType a => Storage -> IO [Head a] +loadHeads s@(Storage { stBacking = StorageDir { dirPath = spath }}) = do +    let hpath = headTypePath spath $ headTypeID @a Proxy + +    files <- filterM (doesFileExist . (hpath </>)) =<< +        handleJust (\e -> guard (isDoesNotExistError e)) (const $ return []) +        (getDirectoryContents hpath) +    fmap catMaybes $ forM files $ \hname -> do +        case U.fromString hname of +             Just hid -> do +                 (h:_) <- BC.lines <$> B.readFile (hpath </> hname) +                 Just ref <- readRef s h +                 return $ Just $ Head (HeadID hid) $ wrappedLoad ref +             Nothing -> return Nothing +loadHeads Storage { stBacking = StorageMemory { memHeads = theads } } = do +    let toHead ((tid, hid), ref) | tid == headTypeID @a Proxy = Just $ Head hid $ wrappedLoad ref +                                 | otherwise                  = Nothing +    catMaybes . map toHead <$> readMVar theads + +loadHead :: forall a. HeadType a => Storage -> HeadID -> IO (Maybe (Head a)) +loadHead s@(Storage { stBacking = StorageDir { dirPath = spath }}) hid = do      handleJust (guard . isDoesNotExistError) (const $ return Nothing) $ do -        let hpath = spath ++ "/heads/" -        (h:_) <- BC.lines <$> B.readFile (hpath ++ hname) +        (h:_) <- BC.lines <$> B.readFile (headPath spath (headTypeID @a Proxy) hid)          Just ref <- readRef s h -        return $ Just $ Head hname ref -loadHead Storage { stBacking = StorageMemory { memHeads = theads } } hname = -    find ((==hname) . headName) <$> readMVar theads - -loadHeadDef :: Storable a => Storage -> String -> IO a -> IO Head -loadHeadDef s hname gen = loadHead s hname >>= \case -        Just h  -> return h -        Nothing -> do obj <- gen -                      Right h <- replaceHead obj (Left (s, hname)) -                      return h - -replaceHead :: Storable a => a -> Either (Storage, String) Head -> IO (Either (Maybe Head) Head) -replaceHead obj prev = do -    let (st, name) = either id (\(Head n (Ref s _)) -> (s, n)) prev -    ref <- store st obj +        return $ Just $ Head hid $ wrappedLoad ref +loadHead Storage { stBacking = StorageMemory { memHeads = theads } } hid = do +    fmap (Head hid . wrappedLoad) . lookup (headTypeID @a Proxy, hid) <$> readMVar theads + +reloadHead :: HeadType a => Head a -> IO (Maybe (Head a)) +reloadHead (Head hid (Stored (Ref st _) _)) = loadHead st hid + +storeHead :: forall a. HeadType a => Storage -> a -> IO (Head a) +storeHead st obj = do +    let tid = headTypeID @a Proxy +    hid <- HeadID <$> U.nextRandom +    stored <- wrappedStore st obj +    case stBacking st of +         StorageDir { dirPath = spath } -> do +             Right () <- writeFileChecked (headPath spath tid hid) Nothing $ +                 showRef (storedRef stored) `B.append` BC.singleton '\n' +             return () +         StorageMemory { memHeads = theads } -> do +             modifyMVar_ theads $ return . (((tid, hid), storedRef stored) :) +    return $ Head hid stored + +replaceHead :: forall a. HeadType a => Head a -> Stored a -> IO (Either (Maybe (Head a)) (Head a)) +replaceHead prev@(Head hid pobj) stored = do +    let st = storedStorage pobj +        tid = headTypeID @a Proxy      case stBacking st of           StorageDir { dirPath = spath } -> do -             let filename = spath ++ "/heads/" ++ name +             let filename = headPath spath tid hid                   showRefL r = showRef r `B.append` BC.singleton '\n' -             writeFileChecked filename (either (const Nothing) (Just . showRefL . headRef) prev) (showRefL ref) >>= \case +             writeFileChecked filename (Just $ showRefL $ headRef prev) (showRefL $ storedRef stored) >>= \case                   Left Nothing -> return $ Left Nothing                   Left (Just bs) -> do Just oref <- readRef st $ BC.takeWhile (/='\n') bs -                                      return $ Left $ Just $ Head name oref -                 Right () -> return $ Right $ Head name ref +                                      return $ Left $ Just $ Head hid $ wrappedLoad oref +                 Right () -> return $ Right $ Head hid stored           StorageMemory { memHeads = theads, memWatchers = twatch } -> do               res <- modifyMVar theads $ \hs -> do -                 ws <- map snd . filter ((==name) . fst) <$> readMVar twatch -                 case (partition ((== name) . headName) hs, prev) of -                      (([], _), Left _)  -> let h = Head name ref -                                             in return (h:hs, Right (h, ws)) -                      (([], _), Right _) -> return (hs, Left Nothing) -                      ((h:_, _), Left _) -> return (hs, Left (Just h)) -                      ((h:_, hs'), Right h') | headRef h == headRef h' -> let nh = Head name ref -                                                                           in return (nh:hs', Right (nh, ws)) -                                             | otherwise -> return (hs, Left (Just h)) +                 ws <- map snd . filter ((==(tid, hid)) . fst) <$> readMVar twatch +                 return $ case partition ((==(tid, hid)) . fst) hs of +                     ([] , _  ) -> (hs, Left Nothing) +                     ((_, r):_, hs') | r == storedRef pobj -> (((tid, hid), storedRef stored) : hs', +                                                                  Right (Head hid stored, ws)) +                                     | otherwise -> (hs, Left $ Just $ Head hid $ wrappedLoad r)               case res of -                  Right (h, ws) -> mapM_ ($h) ws >> return (Right h) +                  Right (h, ws) -> mapM_ ($ headRef h) ws >> return (Right h)                    Left x -> return $ Left x -watchHead :: Head -> (Head -> IO ()) -> IO () -watchHead (Head name (Ref st _)) cb = do +updateHead :: HeadType a => Head a -> (Stored a -> IO (Stored a, b)) -> IO (Maybe (Head a), b) +updateHead h f = do +    (o, x) <- f $ headStoredObject h +    replaceHead h o >>= \case +        Right h' -> return (Just h', x) +        Left Nothing -> return (Nothing, x) +        Left (Just h') -> updateHead h' f + +updateHead_ :: HeadType a => Head a -> (Stored a -> IO (Stored a)) -> IO (Maybe (Head a)) +updateHead_ h = fmap fst . updateHead h . (fmap (,()) .) + +watchHead :: forall a. HeadType a => Head a -> (Head a -> IO ()) -> IO () +watchHead (Head hid (Stored (Ref st _) _)) cb = do +    let cb' = cb . Head hid . wrappedLoad +        tid = headTypeID @a Proxy      case stBacking st of -         StorageDir { dirPath = spath, dirWatchers = mvar } -> modifyMVar_ mvar $ \(mbi, watchers) -> do -             inotify <- (\f -> maybe f return mbi) $ do -                 inotify <- initINotify -                 void $ addWatch inotify [Move] (BC.pack $ spath ++ "/heads") $ \case -                     MovedIn { filePath = fpath } -> do -                         let cname = BC.unpack fpath -                         loadHead st cname >>= \case -                             Just h -> mapM_ ($h) . map snd . filter ((== cname) . fst) . snd =<< readMVar mvar -                             Nothing -> return () -                     _ -> return () -                 return inotify -             return (Just inotify, (name, cb) : watchers) - -         StorageMemory { memWatchers = mvar } -> modifyMVar_ mvar $ return . ((name, cb) :) +         StorageDir { dirPath = spath, dirWatchers = mvar } -> modifyMVar_ mvar $ \(ilist, watchers) -> do +             ilist' <- case lookup tid ilist of +                 Just _ -> return ilist +                 Nothing -> do +                     inotify <- initINotify +                     void $ addWatch inotify [Move] (BC.pack $ headTypePath spath tid) $ \case +                         MovedIn { filePath = fpath } | Just ihid <- HeadID <$> U.fromASCIIBytes fpath -> do +                             loadHead @a st ihid >>= \case +                                 Just h -> mapM_ ($ headRef h) . map snd . filter ((== (tid, ihid)) . fst) . snd =<< readMVar mvar +                                 Nothing -> return () +                         _ -> return () +                     return $ (tid, inotify) : ilist +             return (ilist', ((tid, hid), cb') : watchers) + +         StorageMemory { memWatchers = mvar } -> modifyMVar_ mvar $ return . (((tid, hid), cb') :)  class Storable a where diff --git a/src/Storage/Internal.hs b/src/Storage/Internal.hs index c70e8ae..e4e4f00 100644 --- a/src/Storage/Internal.hs +++ b/src/Storage/Internal.hs @@ -24,6 +24,7 @@ import qualified Data.HashTable.IO as HT  import Data.List  import Data.Map (Map)  import qualified Data.Map as M +import Data.UUID (UUID)  import Foreign.Storable (peek) @@ -57,12 +58,12 @@ showParentStorage Storage { stParent = Just st } = "@" ++ show st  data StorageBacking c           = StorageDir { dirPath :: FilePath -                      , dirWatchers :: MVar (Maybe INotify, [(String, Head' c -> IO ())]) +                      , dirWatchers :: MVar ([(HeadTypeID, INotify)], [((HeadTypeID, HeadID), Ref' c -> IO ())])                        } -         | StorageMemory { memHeads :: MVar [Head' c] +         | StorageMemory { memHeads :: MVar [((HeadTypeID, HeadID), Ref' c)]                           , memObjs :: MVar (Map RefDigest BL.ByteString)                           , memKeys :: MVar (Map RefDigest ScrubbedBytes) -                         , memWatchers :: MVar [(String, Head' c -> IO ())] +                         , memWatchers :: MVar [((HeadTypeID, HeadID), Ref' c -> IO ())]                           }      deriving (Eq) @@ -111,9 +112,15 @@ hashToRefDigest = RefDigest . hashFinalize . hashUpdates hashInit . BL.toChunks  newtype Generation = Generation Int      deriving (Eq, Show) -data Head' c = Head String (Ref' c) +data Head' c a = Head HeadID (Stored' c a)      deriving (Show) +newtype HeadID = HeadID UUID +    deriving (Eq, Ord, Show) + +newtype HeadTypeID = HeadTypeID UUID +    deriving (Eq, Ord) +  data Stored' c a = Stored (Ref' c) a      deriving (Show) |