-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathp2pclient.nim
238 lines (193 loc) · 7.41 KB
/
p2pclient.nim
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
import wNim
import libp2p
import chronos, nimcrypto, strutils
import libp2p/daemon/daemonapi
import strformat
when not(compileOption("threads")):
{.fatal: "Please, compile this program with the --threads:on option!".}
const
ServerProtocols = @["/test-chat-stream"]
type
CustomData = ref object
api: DaemonAPI
remotes: seq[StreamTransport]
consoleFd: AsyncFD
wfd: AsyncFD
serveFut: Future[void]
type
MenuID = enum
idVertical = wIdUser, idHorizontal, idExit, idExample
Example = object
name: string
vfl: string
show: proc ()
let app = App()
let frame = Frame(title="Nim p2p client", size=(900, 600))
let win = Frame(frame, title="console", size=(400, 400))
let panel = Panel(win)
let splitter = Splitter(frame, style=wSpHorizontal or wDoubleBuffered, size=(8, 8))
let statusBar = StatusBar(frame)
let menuBar = MenuBar(frame)
let console = TextCtrl(splitter.panel1, style= wTeRich or wTeMultiLine or wTeDontWrap or wVScroll or wTeReadOnly)
console.font = Font(12, faceName="Consolas", encoding=wFontEncodingCp1252)
var consoleString = ""
var (rfd, wfd) = createAsyncPipe()
var writePipe = fromPipe(wfd)
let command = TextCtrl(splitter.panel2, style= wBorderSunken)
command.font = Font(12, faceName="Consolas", encoding=wFontEncodingCp1252)
command.wEvent_TextEnter do ():
var line = command.getValue()
let res = waitFor writePipe.write(line & "\r\n")
consoleString = line & "\r\n"
console.appendText consoleString
command.clear
template dhtFindPeer() {.dirty.} =
var peerId = PeerID.init(parts[1]).value
consoleString = "= Searching for peer " & peerId.pretty() & "\r\n"
console.appendText consoleString
var id = await udata.api.dhtFindPeer(peerId)
consoleString = "= Peer " & parts[1] & " found at addresses:\r\n"
console.appendText consoleString
for item in id.addresses:
consoleString = $item & "\r\n"
console.appendText consoleString
proc serveThread(udata: CustomData) {.async.} =
{.gcsafe.}:
var transp = fromPipe(udata.consoleFd)
proc remoteReader(transp: StreamTransport) {.async.} =
{.gcsafe.}:
while true:
var line = await transp.readLine()
if len(line) == 0:
break
consoleString = ">> " & line
console.appendText consoleString
while true:
try:
var line = await transp.readLine()
if line.startsWith("/connect"):
var parts = line.split(" ")
if len(parts) == 2:
dhtFindPeer()
var address = MultiAddress.init(multiCodec("p2p-circuit")).value
address = MultiAddress.init(multiCodec("p2p"), peerId).value
consoleString = "= Connecting to peer " & $address & "\r\n"
console.appendText consoleString
echo consoleString
await udata.api.connect(peerId, @[address], 30)
consoleString = "= Opening stream to peer chat " & parts[1] & "\r\n"
console.appendText consoleString
echo consoleString
var stream = await udata.api.openStream(peerId, ServerProtocols)
udata.remotes.add(stream.transp)
consoleString = "= Connected to peer chat " & parts[1] & "\r\n"
console.appendText consoleString
echo consoleString
echo "before remoteReader"
asyncCheck remoteReader(stream.transp)
elif line.startsWith("/search"):
var parts = line.split(" ")
if len(parts) == 2:
dhtFindPeer()
elif line.startsWith("/consearch"):
var parts = line.split(" ")
if len(parts) == 2:
var peerId = PeerID.init(parts[1]).value
consoleString = "= Searching for peers connected to peer " & parts[1] & "\r\n"
console.appendText consoleString
var peers = await udata.api.dhtFindPeersConnectedToPeer(peerId)
consoleString = &"= Found {len(peers)} connected to peer {parts[1]}\r\n"
console.appendText consoleString
for item in peers:
var peer = item.peer
var addresses = newSeq[string]()
var relay = false
for a in item.addresses:
addresses.add($a)
if a.protoName().value == "/p2p-circuit":
relay = true
break
if relay:
consoleString = &"""{peer.pretty()} * [{addresses.join(", ")}]"""
console.appendText consoleString
else:
consoleString = &"""{peer.pretty()} [{addresses.join(", ")}]"""
console.appendText consoleString
elif line.startsWith("/get"):
var parts = line.split(" ")
if len(parts) == 2:
var dag = parts[1]
var value = await udata.api.dhtGetValue dag
echo value
elif line.startsWith("/exit"):
break
else:
var msg = line & "\r\n"
consoleString = "<< " & line
var pending = newSeq[Future[int]]()
for item in udata.remotes:
pending.add(item.write(msg))
if len(pending) > 0:
var results = await all(pending)
except:
consoleString = getCurrentException().msg
proc p2pdaemon() {.thread.} =
{.gcsafe.}:
var data = new CustomData
data.remotes = newSeq[StreamTransport]()
if rfd == asyncInvalidPipe or wfd == asyncInvalidPipe:
raise newException(ValueError, "Could not initialize pipe!")
data.consoleFd = rfd
data.serveFut = serveThread(data)
consoleString = "= Starting P2P node\r\n"
console.appendText consoleString
data.api = waitFor newDaemonApi({DHTFull, Bootstrap})
var id = waitFor data.api.identity()
proc streamHandler(api: DaemonAPI, stream: P2PStream) {.async.} =
{.gcsafe.}:
consoleString = "= Peer " & stream.peer.pretty() & " joined chat\r\n"
console.appendText consoleString
data.remotes.add(stream.transp)
while true:
var line = await stream.transp.readLine()
if len(line) == 0:
break
consoleString = ">> " & line & "\r\n"
console.appendText consoleString
waitFor data.api.addHandler(ServerProtocols, streamHandler)
consoleString = "= Your PeerID is " & id.peer.pretty() & "\r\n"
console.appendText consoleString
waitFor data.serveFut
when isMainModule:
var p2pThread: Thread[void]
p2pThread.createThread p2pdaemon
proc switchSplitter(mode: int) =
splitter.splitMode = mode
statusBar.refresh()
let size = frame.clientSize
splitter.move(size.width div 2, size.height div 2)
let menu = Menu(menuBar, "&Layout")
menu.appendRadioItem(idHorizontal, "&Horizontal").check()
menu.appendRadioItem(idVertical, "&Vertical")
menu.appendSeparator()
menu.append(idExit, "E&xit")
frame.wEvent_Menu do (event: wEvent):
case event.id
of idExit:
frame.close()
of idVertical:
if not splitter.isVertical:
switchSplitter(wSpVertical)
of idHorizontal:
if splitter.isVertical:
switchSplitter(wSpHorizontal)
else:
discard
splitter.panel1.wEvent_Size do ():
splitter.panel1.autolayout "HV:|[console]|"
splitter.panel2.wEvent_Size do ():
splitter.panel2.autolayout "HV:|[command]|"
switchSplitter(wSpHorizontal)
frame.center()
frame.show()
app.mainLoop()