diff options
author | joe <joe@jerkface.net> | 2018-06-06 01:14:12 -0400 |
---|---|---|
committer | joe <joe@jerkface.net> | 2018-06-06 01:14:12 -0400 |
commit | d2fb613ff61d3a8177736f55360ccd08952afc74 (patch) | |
tree | f905c4d1dffcc0346197e113dc75bf26dbc31e28 | |
parent | 891606b5ddf0670657735efeabe5daf4b4f04049 (diff) |
Moved persistence and generic dht api from main module to library.
-rw-r--r-- | dht-client.cabal | 2 | ||||
-rw-r--r-- | examples/dhtd.hs | 108 | ||||
-rw-r--r-- | src/Network/Kademlia/CommonAPI.hs | 84 | ||||
-rw-r--r-- | src/Network/Kademlia/Persistence.hs | 51 |
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 | |||
72 | import Network.Address hiding (NodeId, NodeInfo(..)) | 72 | import Network.Address hiding (NodeId, NodeInfo(..)) |
73 | import Network.QueryResponse | 73 | import Network.QueryResponse |
74 | import Network.StreamServer | 74 | import Network.StreamServer |
75 | import Network.Kademlia.CommonAPI | ||
76 | import Network.Kademlia.Persistence | ||
77 | import Network.Kademlia.Routing as R | ||
75 | import Network.Kademlia.Search | 78 | import Network.Kademlia.Search |
76 | import qualified Network.BitTorrent.MainlineDHT as Mainline | 79 | import qualified Network.BitTorrent.MainlineDHT as Mainline |
77 | import qualified Network.Tox as Tox | 80 | import qualified Network.Tox as Tox |
78 | import Network.Kademlia.Routing as R | ||
79 | import Data.Aeson as J (ToJSON, FromJSON) | ||
80 | import qualified Data.Aeson as J | ||
81 | import qualified Data.ByteString.Lazy as L | 81 | import qualified Data.ByteString.Lazy as L |
82 | import qualified Data.ByteString.Char8 as B | 82 | import qualified Data.ByteString.Char8 as B |
83 | import qualified Data.Text as T | 83 | import 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 | ||
162 | data 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 | |||
174 | data 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 | |||
192 | data 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 | |||
199 | data DHTPing ni = forall r. DHTPing | ||
200 | { pingQuery :: [String] -> ni -> IO (Maybe r) | ||
201 | , pingShowResult :: r -> String | ||
202 | } | ||
203 | |||
204 | data 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 | |||
228 | nodesFileName :: String -> String | ||
229 | nodesFileName netname = netname ++ "-nodes.json" | ||
230 | |||
231 | saveNodes :: String -> DHT -> IO () | ||
232 | saveNodes 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 | |||
239 | loadNodes :: FromJSON ni => String -> IO [ni] | ||
240 | loadNodes 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 | |||
247 | fallbackLoad :: FromJSON t => FilePath -> IO [t] | ||
248 | fallbackLoad 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 | {- |
266 | pingNodes :: String -> DHT -> IO Bool | 164 | pingNodes :: 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 #-} | ||
2 | module Network.Kademlia.CommonAPI where | ||
3 | |||
4 | |||
5 | import Control.Concurrent | ||
6 | import Control.Concurrent.STM | ||
7 | import Data.Aeson as J (FromJSON, ToJSON) | ||
8 | import Data.Hashable | ||
9 | import qualified Data.Map as Map | ||
10 | import Data.Serialize as S | ||
11 | import qualified Data.Set as Set | ||
12 | import Data.Time.Clock.POSIX | ||
13 | import Data.Typeable | ||
14 | |||
15 | import Network.Kademlia.Search | ||
16 | import Network.Kademlia.Routing as R | ||
17 | import Crypto.Tox (SecretKey,PublicKey) | ||
18 | |||
19 | data 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 | |||
43 | data 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 | |||
55 | data 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 | |||
73 | data 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 | |||
80 | data 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 #-} | ||
2 | module Network.Kademlia.Persistence where | ||
3 | |||
4 | import Network.Kademlia.CommonAPI | ||
5 | import Network.Kademlia.Routing as R | ||
6 | |||
7 | import Control.Concurrent.STM | ||
8 | import qualified Data.Aeson as J | ||
9 | ;import Data.Aeson as J (FromJSON) | ||
10 | import qualified Data.ByteString.Lazy as L | ||
11 | import qualified Data.HashMap.Strict as HashMap | ||
12 | import Data.List | ||
13 | import qualified Data.Vector as V | ||
14 | import System.IO.Error | ||
15 | |||
16 | saveNodes :: String -> DHT -> IO () | ||
17 | saveNodes 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 | |||
24 | loadNodes :: FromJSON ni => String -> IO [ni] | ||
25 | loadNodes 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 | |||
32 | nodesFileName :: String -> String | ||
33 | nodesFileName netname = netname ++ "-nodes.json" | ||
34 | |||
35 | fallbackLoad :: FromJSON t => FilePath -> IO [t] | ||
36 | fallbackLoad 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 | |||