diff --git a/obelisk/frontend/frontend.cabal b/obelisk/frontend/frontend.cabal index 7c899e8f045771ae8fd309eeddd0a7c3cb2872b9..229453e46512bcb711c51efa1792f2e50ab6ef4e 100644 --- a/obelisk/frontend/frontend.cabal +++ b/obelisk/frontend/frontend.cabal @@ -45,6 +45,7 @@ library FrontendPaths Pages.Folder Pages.TechDemo + Pages.DownloadOneCap ghc-options: -Wall -Wredundant-constraints -Wincomplete-uni-patterns -Wincomplete-record-updates -O -fno-show-valid-hole-fits executable frontend diff --git a/obelisk/frontend/src/Controller.hs b/obelisk/frontend/src/Controller.hs index a91a28560345390cfe56801d4372d5ba54d517b0..092aa64ab4fb8c34057a6df3bd192f1c92db9b24 100644 --- a/obelisk/frontend/src/Controller.hs +++ b/obelisk/frontend/src/Controller.hs @@ -11,6 +11,7 @@ import Reflex.Dom.Core import Pages.Folder (folderPage) import Pages.TechDemo (techDemoPage) +import Pages.DownloadOneCap (downloadOneCapPage) -- dyn :: (Adjustable t m, NotReady t m, PostBuild t m) => Dynamic t (m a) -> m (Event t a) -- constDyn :: Reflex t => a -> Dynamic t a @@ -25,7 +26,7 @@ import Pages.TechDemo (techDemoPage) -- button :: DomBuilder t m => Text -> m (Event t ()) -- | An enumeration of the views to which it is possible to switch. -data WhichPage = TechDemo | FolderPage +data WhichPage = TechDemo | FolderPage | DownloadOneCap -- | A view onto whichever page is currently active, as determined by user -- inputs to the app. @@ -40,7 +41,7 @@ activePage = let activePageEv = pickPage <$> switchEv -- Dynamic (Event t WhichPage) - widgetDyn <- widgetHold (pickPage FolderPage) activePageEv + widgetDyn <- widgetHold (pickPage DownloadOneCap) activePageEv -- Behavior t (Event t WhichPage) let widgetBhr = current widgetDyn @@ -57,3 +58,4 @@ activePage = pickPage :: ObeliskWidget t r m => WhichPage -> m (Event t WhichPage) pickPage TechDemo = fmap (FolderPage <$) techDemoPage pickPage FolderPage = fmap (TechDemo <$) folderPage +pickPage DownloadOneCap = fmap (FolderPage <$) downloadOneCapPage diff --git a/obelisk/frontend/src/Pages/DownloadOneCap.hs b/obelisk/frontend/src/Pages/DownloadOneCap.hs new file mode 100644 index 0000000000000000000000000000000000000000..42cbb881b00c99980cd22588649d5c5aa5b44b8a --- /dev/null +++ b/obelisk/frontend/src/Pages/DownloadOneCap.hs @@ -0,0 +1,45 @@ +{-# LANGUAGE NamedFieldPuns #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE TypeFamilies #-} + +module Pages.DownloadOneCap (downloadOneCapPage) where + +import Control.Monad.IO.Class (liftIO) +import Data.List.NonEmpty (NonEmpty(..)) + +import Obelisk.Frontend +import Reflex.Dom.Core + +import Tahoe.CHK.Types (StorageServer) +import Tahoe.CHK.Capability (pCapability) +import Text.Megaparsec (parse) + +import TahoeDownloader + +data TahoeConfiguration = + TahoeConfiguration + { storageServers :: NonEmpty StorageServer + } deriving (Eq, Ord, Show) + +downloadOneCapPage :: ObeliskWidget t r m => m (Event t ()) +downloadOneCapPage TahoeConfiguration { storageServers } = do + -- Place an input field into the page and retain a reference to it. + el "span" $ text "Download: " + capInputWidget <- inputElement def + + -- `Dynamic t T.Text` holding the value of the text input. + let capTextDyn = _inputElement_value capInputWidget + + -- `Dynamic t CHK` holding parsed capabilities. + let parsedCap = filterRight $ parse pCapability "" <$> capTextDyn + + sharesEv <- performEvent $ ffor parsedCap $ liftIO . downloadShareBytes $ \case -> + (CHKVerifier v) -> v + (CHKReader (Reader { verifier })) -> verifier + + plaintextEv <- decodeFull <$> sharesEv + plaintextDyn <- holdDyn "" plaintextEv + + el "div" $ do + el "div" $ text "Plaintext: " + el "div" $ dynText plaintextDyn diff --git a/obelisk/frontend/src/TahoeDownloader.hs b/obelisk/frontend/src/TahoeDownloader.hs new file mode 100644 index 0000000000000000000000000000000000000000..b857a238fb5c0542b0c3527ae9e506ebcbaede04 --- /dev/null +++ b/obelisk/frontend/src/TahoeDownloader.hs @@ -0,0 +1,55 @@ +{-# LANGUAGE NamedFieldPuns #-} + +module TahoeDownloader (downloadShares) where + +import Tahoe.CHK.Decode (Share) +import Tahoe.CHK.Capability (Verifier(..)) +import Tahoe.CHK.Types (StorageServer) +import qualified Data.ByteString as BS + +type ShareNumber = Int + +type ShareMap = Map ShareNumber [StorageServer] + +-- | Identify which servers claim to have some data at some index. +locateShares :: + -- | All of the servers which could possibly have the data. These "could + -- possibly" have the data because local configuration suggests the data + -- might have been uploaded to them in the past. + [StorageServer] -> + -- | The storage index at which to look for data. + StorageIndex -> + -- | The share numbers to look for. + Set ShareNumber -> + -- | A mapping from share number to servers which claim to have the data. + IO () + +-- | Download the bytes for one share number at some index from some server. +downloadShare :: + -- | All of the servers which likely have a certain share number at the + -- index. These "likely" have the data because a recent ``locateShares`` + -- call found it there. + [StorageServer] -> + -- | The storage index at which to look for data. + StorageIndex -> + -- | The share number to look for. + ShareNumber -> + -- | Either an error encountered trying to get the data or the data itself. + IO (Either DownloadError ByteString) + +type Required = Int + +-- | Find and download enough shares to decode the data identified by the given capability. +fetchShares :: + [StorageServer] -> + StorageIndex -> + Required -> + IO [(ShareNumber, ByteString)] + +-- | Maybe something like the number of currently valid shares that can be lost +-- before the object is lost. +type Health = Int + +-- from tahoe-chk +-- verifyFull :: Verifier -> [(ShareNumber, ByteString)] -> Either VerifyError Health +-- decodeFull :: Reader -> [(ShareNumber, ByteString)] -> Either VerifyError ByteString