From 5c2384ff4d4752a3fadf08b56fd370c778153dbf Mon Sep 17 00:00:00 2001
From: Jean-Paul Calderone <exarkun@twistedmatrix.com>
Date: Thu, 1 Jun 2023 13:27:47 -0400
Subject: [PATCH] Add a `lookupServer` for mutables

This is clunky but it works ... for now.
---
 gbs-downloader.cabal                     |  2 +
 src/Tahoe/Download.hs                    |  2 +
 src/Tahoe/Download/Internal/Client.hs    | 75 ++++++++++++++++++++-
 src/Tahoe/Download/Internal/Immutable.hs | 84 +++---------------------
 src/Tahoe/Download/Internal/Mutable.hs   | 15 +++++
 5 files changed, 102 insertions(+), 76 deletions(-)
 create mode 100644 src/Tahoe/Download/Internal/Mutable.hs

diff --git a/gbs-downloader.cabal b/gbs-downloader.cabal
index 842eb28..908ee89 100644
--- a/gbs-downloader.cabal
+++ b/gbs-downloader.cabal
@@ -84,6 +84,7 @@ library
     Tahoe.Download.Internal.Capability
     Tahoe.Download.Internal.Client
     Tahoe.Download.Internal.Immutable
+    Tahoe.Download.Internal.Mutable
 
   -- Modules included in this library but not exported.
   -- other-modules:
@@ -105,6 +106,7 @@ library
     , exceptions
     , http-client
     , http-client-tls
+    , http-types
     , network-uri
     , servant-client
     , servant-client-core
diff --git a/src/Tahoe/Download.hs b/src/Tahoe/Download.hs
index 19a7a46..93488ce 100644
--- a/src/Tahoe/Download.hs
+++ b/src/Tahoe/Download.hs
@@ -11,6 +11,7 @@ module Tahoe.Download (
     discoverShares,
     download,
     announcementToImmutableStorageServer,
+    announcementToMutableStorageServer,
     getShareNumbers,
 ) where
 
@@ -30,6 +31,7 @@ import Tahoe.CHK.Types (ShareNum, StorageIndex)
 import Tahoe.Download.Internal.Capability
 import Tahoe.Download.Internal.Client
 import Tahoe.Download.Internal.Immutable
+import Tahoe.Download.Internal.Mutable
 
 -- | Partially describe one share download.
 type DownloadTask = (ShareNum, StorageServer)
diff --git a/src/Tahoe/Download/Internal/Client.hs b/src/Tahoe/Download/Internal/Client.hs
index 4b81131..43cc02e 100644
--- a/src/Tahoe/Download/Internal/Client.hs
+++ b/src/Tahoe/Download/Internal/Client.hs
@@ -3,19 +3,25 @@
 -}
 module Tahoe.Download.Internal.Client where
 
+import Control.Exception
 import Control.Monad.IO.Class
 import qualified Data.ByteString as B
+import Data.ByteString.Base32
 import qualified Data.ByteString.Base64 as Base64
+import qualified Data.Set as Set
 import qualified Data.Text as T
 import Data.Text.Encoding
 import Network.Connection
-import Network.HTTP.Client
+import Network.HTTP.Client (Manager, ManagerSettings (managerModifyRequest), Request (requestHeaders))
 import Network.HTTP.Client.TLS
+import Network.HTTP.Types (ByteRange)
 import Servant.Client
 import Tahoe.Announcement
 import Tahoe.CHK.Server (
-    StorageServer,
+    StorageServer (..),
  )
+import TahoeLAFS.Storage.API (CBORSet (CBORSet), ShareNumber (ShareNumber))
+import Text.Read (readMaybe)
 
 -- | Make an HTTPS URL.
 https :: String -> Int -> BaseUrl
@@ -125,3 +131,68 @@ data LookupError
  representation of some value.
 -}
 data DeserializeError = UnknownDeserializeError -- add more later?
+
+type GetShareNumbers = String -> ClientM (CBORSet ShareNumber)
+type ReadShare = String -> ShareNumber -> Maybe [ByteRange] -> ClientM B.ByteString
+
+{- | Create a StorageServer that will speak Great Black Swamp using the given
+ manager to the server at the given host/port.
+-}
+mkWrapper :: GetShareNumbers -> ReadShare -> Manager -> [Char] -> Int -> StorageServer
+mkWrapper getShareNumbers readShare manager host realPort =
+    StorageServer{..}
+  where
+    baseUrl = https host realPort
+    env = mkClientEnv manager baseUrl
+    toBase32 = T.unpack . T.toLower . encodeBase32Unpadded
+
+    storageServerID = undefined
+
+    storageServerWrite = undefined
+
+    storageServerRead storageIndex shareNum = do
+        let clientm = readShare (toBase32 storageIndex) (ShareNumber $ fromIntegral shareNum) Nothing
+        res <- runClientM clientm env
+        case res of
+            Left err -> do
+                throwIO err
+            Right bs -> pure bs
+
+    storageServerGetBuckets storageIndex = do
+        let clientm = getShareNumbers (toBase32 storageIndex)
+        r <- try $ runClientM clientm env
+        case r of
+            Left (_ :: SomeException) -> do
+                pure mempty
+            Right res -> do
+                case res of
+                    Left err -> do
+                        throwIO err
+                    Right (CBORSet s) -> pure $ Set.map (\(ShareNumber i) -> fromIntegral i) s -- XXX fromIntegral aaaaaaaa!!
+
+{- | If possible, populate a StorageServer with functions for operating on data
+  on the server at the given URI.
+-}
+makeServer :: MonadIO m => GetShareNumbers -> ReadShare -> URI -> m (Either LookupError StorageServer)
+makeServer
+    getShareNumbers
+    readShare
+    URI
+        { uriScheme = "pb:"
+        , uriAuthority = Just URIAuth{uriUserInfo = tubid, uriRegName = host, uriPort = (':' : port)}
+        , uriPath = ('/' : swissnum)
+        , uriFragment = "" -- It's a fURL, not a NURL, so there's no fragment.
+        } =
+        case readMaybe port of
+            Nothing -> pure . Left . PortParseError $ port
+            Just realPort -> do
+                manager <- liftIO $ newGBSManager tubid swissnum
+
+                pure . Right $ mkWrapper getShareNumbers readShare manager host realPort
+makeServer _ _ _ = pure . Left $ AnnouncementStructureUnmatched
+
+announcementToStorageServer :: MonadIO m => GetShareNumbers -> ReadShare -> StorageServerAnnouncement -> m (Either LookupError StorageServer)
+announcementToStorageServer getShareNumbers readShare ann =
+    case greatBlackSwampURIs ann of
+        Nothing -> pure . Left . URIParseError $ ann
+        Just uri -> makeServer getShareNumbers readShare uri
diff --git a/src/Tahoe/Download/Internal/Immutable.hs b/src/Tahoe/Download/Internal/Immutable.hs
index f7c9cb8..00b7efb 100644
--- a/src/Tahoe/Download/Internal/Immutable.hs
+++ b/src/Tahoe/Download/Internal/Immutable.hs
@@ -1,79 +1,15 @@
 -- | Functionality related to retrieving "immutable" shares (mainly CHK).
 module Tahoe.Download.Internal.Immutable where
 
-import Control.Exception
-import Control.Monad.IO.Class
-import Data.ByteString.Base32
-import qualified Data.Set as Set
-import qualified Data.Text as T
-import Network.HTTP.Client (Manager)
-import Servant.Client
-import Tahoe.Announcement
-import Tahoe.CHK.Server (StorageServer (..))
-import Tahoe.Download.Internal.Client
-import TahoeLAFS.Storage.API (CBORSet (CBORSet), ShareNumber (ShareNumber))
-import TahoeLAFS.Storage.Client
-import Text.Read (readMaybe)
-
-{- | Create a StorageServer that will speak Great Black Swamp using the given
- manager to the server at the given host/port.
--}
-mkImmutableWrapper :: Manager -> [Char] -> Int -> StorageServer
-mkImmutableWrapper manager host realPort =
-    StorageServer{..}
-  where
-    baseUrl = https host realPort
-    env = mkClientEnv manager baseUrl
-    toBase32 = T.unpack . T.toLower . encodeBase32Unpadded
-
-    storageServerID = undefined
-
-    storageServerWrite = undefined
-
-    storageServerRead storageIndex shareNum = do
-        let clientm = readImmutableShare (toBase32 storageIndex) (ShareNumber $ fromIntegral shareNum) Nothing
-        res <- runClientM clientm env
-        case res of
-            Left err -> do
-                throwIO err
-            Right bs -> pure bs
-
-    storageServerGetBuckets storageIndex = do
-        let clientm = getImmutableShareNumbers (toBase32 storageIndex)
-        r <- try $ runClientM clientm env
-        case r of
-            Left (_ :: SomeException) -> do
-                pure mempty
-            Right res -> do
-                case res of
-                    Left err -> do
-                        throwIO err
-                    Right (CBORSet s) -> pure $ Set.map (\(ShareNumber i) -> fromIntegral i) s -- XXX fromIntegral aaaaaaaa!!
-
-{- | Interpret the location in an announcement as a Tahoe-LAFS fURL pointed at
- a Great Black Swamp server.
+import Control.Monad.IO.Class (MonadIO)
+import Tahoe.Announcement (StorageServerAnnouncement)
+import Tahoe.CHK.Server (StorageServer)
+import Tahoe.Download.Internal.Client (LookupError, announcementToStorageServer)
+import TahoeLAFS.Storage.Client (getImmutableShareNumbers, readImmutableShare)
+
+{- | Interpret the location in an announcement as a Tahoe-LAFS fURL pointed at a
+ Great Black Swamp server and construct a StorageServer for interacting with
+ immutable shares stored on it.
 -}
 announcementToImmutableStorageServer :: MonadIO m => StorageServerAnnouncement -> m (Either LookupError StorageServer)
-announcementToImmutableStorageServer ann =
-    case greatBlackSwampURIs ann of
-        Nothing -> pure . Left . URIParseError $ ann
-        Just uri -> makeImmutableServer uri
-
-{- | If possible, populate a StorageServer with functions for operating on
- immutable data on the server at the given URI.
--}
-makeImmutableServer :: MonadIO m => URI -> m (Either LookupError StorageServer)
-makeImmutableServer
-    URI
-        { uriScheme = "pb:"
-        , uriAuthority = Just URIAuth{uriUserInfo = tubid, uriRegName = host, uriPort = (':' : port)}
-        , uriPath = ('/' : swissnum)
-        , uriFragment = "" -- It's a fURL, not a NURL, so there's no fragment.
-        } =
-        case readMaybe port of
-            Nothing -> pure . Left . PortParseError $ port
-            Just realPort -> do
-                manager <- liftIO $ newGBSManager tubid swissnum
-
-                pure . Right $ mkImmutableWrapper manager host realPort
-makeImmutableServer _ = pure . Left $ AnnouncementStructureUnmatched
+announcementToImmutableStorageServer = announcementToStorageServer getImmutableShareNumbers readImmutableShare
diff --git a/src/Tahoe/Download/Internal/Mutable.hs b/src/Tahoe/Download/Internal/Mutable.hs
new file mode 100644
index 0000000..011e601
--- /dev/null
+++ b/src/Tahoe/Download/Internal/Mutable.hs
@@ -0,0 +1,15 @@
+-- | Functionality related to retrieving "mutable" shares (for example, SDMF).
+module Tahoe.Download.Internal.Mutable where
+
+import Control.Monad.IO.Class (MonadIO)
+import Tahoe.Announcement (StorageServerAnnouncement)
+import Tahoe.CHK.Server (StorageServer)
+import Tahoe.Download.Internal.Client (LookupError, announcementToStorageServer)
+import TahoeLAFS.Storage.Client (getMutableShareNumbers, readMutableShares)
+
+{- | Interpret the location in an announcement as a Tahoe-LAFS fURL pointed at a
+ Great Black Swamp server and construct a StorageServer for interacting with
+ mutable shares stored on it.
+-}
+announcementToMutableStorageServer :: MonadIO m => StorageServerAnnouncement -> m (Either LookupError StorageServer)
+announcementToMutableStorageServer = announcementToStorageServer getMutableShareNumbers readMutableShares
-- 
GitLab