summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorjoe <joe@jerkface.net>2018-06-06 01:14:12 -0400
committerjoe <joe@jerkface.net>2018-06-06 01:14:12 -0400
commitd2fb613ff61d3a8177736f55360ccd08952afc74 (patch)
treef905c4d1dffcc0346197e113dc75bf26dbc31e28
parent891606b5ddf0670657735efeabe5daf4b4f04049 (diff)
Moved persistence and generic dht api from main module to library.
-rw-r--r--dht-client.cabal2
-rw-r--r--examples/dhtd.hs108
-rw-r--r--src/Network/Kademlia/CommonAPI.hs84
-rw-r--r--src/Network/Kademlia/Persistence.hs51
4 files changed, 140 insertions, 105 deletions
diff --git a/dht-client.cabal b/dht-client.cabal
index 496c0f5a..a3bb64fc 100644
--- a/dht-client.cabal
+++ b/dht-client.cabal
@@ -83,6 +83,8 @@ library
83 Data.BEncode.Pretty 83 Data.BEncode.Pretty
84 Control.Concurrent.Tasks 84 Control.Concurrent.Tasks
85 Network.Kademlia 85 Network.Kademlia
86 Network.Kademlia.CommonAPI
87 Network.Kademlia.Persistence
86 Network.BitTorrent.MainlineDHT 88 Network.BitTorrent.MainlineDHT
87 Network.BitTorrent.MainlineDHT.Symbols 89 Network.BitTorrent.MainlineDHT.Symbols
88 System.Global6 90 System.Global6
diff --git a/examples/dhtd.hs b/examples/dhtd.hs
index fb42597e..67507634 100644
--- a/examples/dhtd.hs
+++ b/examples/dhtd.hs
@@ -72,12 +72,12 @@ import Network.UPNP as UPNP
72import Network.Address hiding (NodeId, NodeInfo(..)) 72import Network.Address hiding (NodeId, NodeInfo(..))
73import Network.QueryResponse 73import Network.QueryResponse
74import Network.StreamServer 74import Network.StreamServer
75import Network.Kademlia.CommonAPI
76import Network.Kademlia.Persistence
77import Network.Kademlia.Routing as R
75import Network.Kademlia.Search 78import Network.Kademlia.Search
76import qualified Network.BitTorrent.MainlineDHT as Mainline 79import qualified Network.BitTorrent.MainlineDHT as Mainline
77import qualified Network.Tox as Tox 80import qualified Network.Tox as Tox
78import Network.Kademlia.Routing as R
79import Data.Aeson as J (ToJSON, FromJSON)
80import qualified Data.Aeson as J
81import qualified Data.ByteString.Lazy as L 81import qualified Data.ByteString.Lazy as L
82import qualified Data.ByteString.Char8 as B 82import qualified Data.ByteString.Char8 as B
83import qualified Data.Text as T 83import qualified Data.Text as T
@@ -159,108 +159,6 @@ hPutClientChunk (ClientHandle h hstate) s = do
159 hPutStr h (' ' : marshalForClient s) 159 hPutStr h (' ' : marshalForClient s)
160 putMVar hstate 2 -- ready for more output 160 putMVar hstate 2 -- ready for more output
161 161
162data DHTQuery nid ni = forall addr r tok.
163 ( Ord addr
164 , Typeable r
165 , Typeable tok
166 , Typeable ni
167 ) => DHTQuery
168 { qsearch :: Search nid addr tok ni r
169 , qhandler :: ni -> nid -> IO ([ni], [r], Maybe tok) -- ^ Invoked on local node, when there is no query destination.
170 , qshowR :: r -> String
171 , qshowTok :: tok -> Maybe String
172 }
173
174data DHTAnnouncable nid = forall dta tok ni r.
175 ( Show r
176 , Typeable dta -- information being announced
177 , Typeable tok -- token
178 , Typeable r -- search result
179 , Typeable ni -- node
180 ) => DHTAnnouncable
181 { announceParseData :: String -> Either String dta
182 , announceParseToken :: dta -> String -> Either String tok
183 , announceParseAddress :: String -> Either String ni
184 , announceSendData :: Either ( String {- search name -}
185 , String -> Either String r
186 , PublicKey {- me -} -> dta -> r -> IO ())
187 (dta -> tok -> Maybe ni -> IO (Maybe r))
188 , announceInterval :: POSIXTime
189 , announceTarget :: dta -> nid
190 }
191
192data DHTSearch nid ni = forall addr tok r. DHTSearch
193 { searchThread :: ThreadId
194 , searchState :: SearchState nid addr tok ni r
195 , searchShowTok :: tok -> Maybe String
196 , searchResults :: TVar (Set.Set String)
197 }
198
199data DHTPing ni = forall r. DHTPing
200 { pingQuery :: [String] -> ni -> IO (Maybe r)
201 , pingShowResult :: r -> String
202 }
203
204data DHT = forall nid ni. ( Show ni
205 , Read ni
206 , ToJSON ni
207 , FromJSON ni
208 , Ord ni
209 , Hashable ni
210 , Show nid
211 , Ord nid
212 , Hashable nid
213 , Typeable ni
214 , S.Serialize nid
215 ) =>
216 DHT
217 { dhtBuckets :: TVar (BucketList ni)
218 , dhtSecretKey :: STM (Maybe SecretKey)
219 , dhtPing :: Map.Map String (DHTPing ni)
220 , dhtQuery :: Map.Map String (DHTQuery nid ni)
221 , dhtAnnouncables :: Map.Map String (DHTAnnouncable nid)
222 , dhtParseId :: String -> Either String nid
223 , dhtSearches :: TVar (Map.Map (String,nid) (DHTSearch nid ni))
224 , dhtFallbackNodes :: IO [ni]
225 , dhtBootstrap :: [ni] -> [ni] -> IO ()
226 }
227
228nodesFileName :: String -> String
229nodesFileName netname = netname ++ "-nodes.json"
230
231saveNodes :: String -> DHT -> IO ()
232saveNodes netname DHT{dhtBuckets} = do
233 bkts <- atomically $ readTVar dhtBuckets
234 let ns = map fst $ concat $ R.toList bkts
235 bs = J.encode ns
236 fname = nodesFileName netname
237 L.writeFile fname bs
238
239loadNodes :: FromJSON ni => String -> IO [ni]
240loadNodes netname = do
241 let fname = nodesFileName netname
242 attempt <- tryIOError $ do
243 J.decode <$> L.readFile fname
244 >>= maybe (ioError $ userError "Nothing") return
245 either (const $ fallbackLoad fname) return attempt
246
247fallbackLoad :: FromJSON t => FilePath -> IO [t]
248fallbackLoad fname = do
249 attempt <- tryIOError $ do
250 J.decode <$> L.readFile fname
251 >>= maybe (ioError $ userError "Nothing") return
252 let go r = do
253 let m = HashMap.lookup "nodes" (r :: J.Object)
254 ns0 = case m of Just (J.Array v) -> V.toList v
255 Nothing -> []
256 ns1 = zip (map J.fromJSON ns0) ns0
257 issuc (J.Error _,_) = False
258 issuc _ = True
259 (ss,fs) = partition issuc ns1
260 ns = map (\(J.Success n,_) -> n) ss
261 mapM_ print (map snd fs) >> return ns
262 either (const $ return []) go attempt
263
264 162
265{- 163{-
266pingNodes :: String -> DHT -> IO Bool 164pingNodes :: String -> DHT -> IO Bool
diff --git a/src/Network/Kademlia/CommonAPI.hs b/src/Network/Kademlia/CommonAPI.hs
new file mode 100644
index 00000000..601be5d8
--- /dev/null
+++ b/src/Network/Kademlia/CommonAPI.hs
@@ -0,0 +1,84 @@
1{-# LANGUAGE ExistentialQuantification #-}
2module Network.Kademlia.CommonAPI where
3
4
5import Control.Concurrent
6import Control.Concurrent.STM
7import Data.Aeson as J (FromJSON, ToJSON)
8import Data.Hashable
9import qualified Data.Map as Map
10import Data.Serialize as S
11import qualified Data.Set as Set
12import Data.Time.Clock.POSIX
13import Data.Typeable
14
15import Network.Kademlia.Search
16import Network.Kademlia.Routing as R
17import Crypto.Tox (SecretKey,PublicKey)
18
19data DHT = forall nid ni. ( Show ni
20 , Read ni
21 , ToJSON ni
22 , FromJSON ni
23 , Ord ni
24 , Hashable ni
25 , Show nid
26 , Ord nid
27 , Hashable nid
28 , Typeable ni
29 , S.Serialize nid
30 ) =>
31 DHT
32 { dhtBuckets :: TVar (BucketList ni)
33 , dhtSecretKey :: STM (Maybe SecretKey)
34 , dhtPing :: Map.Map String (DHTPing ni)
35 , dhtQuery :: Map.Map String (DHTQuery nid ni)
36 , dhtAnnouncables :: Map.Map String (DHTAnnouncable nid)
37 , dhtParseId :: String -> Either String nid
38 , dhtSearches :: TVar (Map.Map (String,nid) (DHTSearch nid ni))
39 , dhtFallbackNodes :: IO [ni]
40 , dhtBootstrap :: [ni] -> [ni] -> IO ()
41 }
42
43data DHTQuery nid ni = forall addr r tok.
44 ( Ord addr
45 , Typeable r
46 , Typeable tok
47 , Typeable ni
48 ) => DHTQuery
49 { qsearch :: Search nid addr tok ni r
50 , qhandler :: ni -> nid -> IO ([ni], [r], Maybe tok) -- ^ Invoked on local node, when there is no query destination.
51 , qshowR :: r -> String
52 , qshowTok :: tok -> Maybe String
53 }
54
55data DHTAnnouncable nid = forall dta tok ni r.
56 ( Show r
57 , Typeable dta -- information being announced
58 , Typeable tok -- token
59 , Typeable r -- search result
60 , Typeable ni -- node
61 ) => DHTAnnouncable
62 { announceParseData :: String -> Either String dta
63 , announceParseToken :: dta -> String -> Either String tok
64 , announceParseAddress :: String -> Either String ni
65 , announceSendData :: Either ( String {- search name -}
66 , String -> Either String r
67 , PublicKey {- me -} -> dta -> r -> IO ())
68 (dta -> tok -> Maybe ni -> IO (Maybe r))
69 , announceInterval :: POSIXTime
70 , announceTarget :: dta -> nid
71 }
72
73data DHTSearch nid ni = forall addr tok r. DHTSearch
74 { searchThread :: ThreadId
75 , searchState :: SearchState nid addr tok ni r
76 , searchShowTok :: tok -> Maybe String
77 , searchResults :: TVar (Set.Set String)
78 }
79
80data DHTPing ni = forall r. DHTPing
81 { pingQuery :: [String] -> ni -> IO (Maybe r)
82 , pingShowResult :: r -> String
83 }
84
diff --git a/src/Network/Kademlia/Persistence.hs b/src/Network/Kademlia/Persistence.hs
new file mode 100644
index 00000000..d7431671
--- /dev/null
+++ b/src/Network/Kademlia/Persistence.hs
@@ -0,0 +1,51 @@
1{-# LANGUAGE NamedFieldPuns #-}
2module Network.Kademlia.Persistence where
3
4import Network.Kademlia.CommonAPI
5import Network.Kademlia.Routing as R
6
7import Control.Concurrent.STM
8import qualified Data.Aeson as J
9 ;import Data.Aeson as J (FromJSON)
10import qualified Data.ByteString.Lazy as L
11import qualified Data.HashMap.Strict as HashMap
12import Data.List
13import qualified Data.Vector as V
14import System.IO.Error
15
16saveNodes :: String -> DHT -> IO ()
17saveNodes netname DHT{dhtBuckets} = do
18 bkts <- atomically $ readTVar dhtBuckets
19 let ns = map fst $ concat $ R.toList bkts
20 bs = J.encode ns
21 fname = nodesFileName netname
22 L.writeFile fname bs
23
24loadNodes :: FromJSON ni => String -> IO [ni]
25loadNodes netname = do
26 let fname = nodesFileName netname
27 attempt <- tryIOError $ do
28 J.decode <$> L.readFile fname
29 >>= maybe (ioError $ userError "Nothing") return
30 either (const $ fallbackLoad fname) return attempt
31
32nodesFileName :: String -> String
33nodesFileName netname = netname ++ "-nodes.json"
34
35fallbackLoad :: FromJSON t => FilePath -> IO [t]
36fallbackLoad fname = do
37 attempt <- tryIOError $ do
38 J.decode <$> L.readFile fname
39 >>= maybe (ioError $ userError "Nothing") return
40 let go r = do
41 let m = HashMap.lookup "nodes" (r :: J.Object)
42 ns0 = case m of Just (J.Array v) -> V.toList v
43 Nothing -> []
44 ns1 = zip (map J.fromJSON ns0) ns0
45 issuc (J.Error _,_) = False
46 issuc _ = True
47 (ss,fs) = partition issuc ns1
48 ns = map (\(J.Success n,_) -> n) ss
49 mapM_ print (map snd fs) >> return ns
50 either (const $ return []) go attempt
51