-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathclient.py
executable file
·222 lines (159 loc) · 6.43 KB
/
client.py
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
#!/usr/bin/env python3
import atexit
import os
import socket
import sys
import constant
from peer import Peer
from track import Track
class Client:
def __init__(self, cli):
self.connections = {}
self.cli = cli
self.all_tracks = {}
self.all_tracks_sh = {} # Short hash
self.local_tracks = {}
self.should_update = True
# Make sure the connection list gets stored to disk
atexit.register(lambda: Peer.dump_to_disk(self.connections.values()))
def add_tracks(self, track_list):
'''
Add tracks to the list of available tracks.
'''
for track in track_list:
short_hash = track.short_hash()
# Watch for hash collisions but don't chain for now
if (short_hash in self.all_tracks_sh):
if (self.all_tracks_sh[short_hash].hash != track.hash):
self.cli.log('HASH COLLISION!!!')
# Don't overwrite the local track info that we have
if (
track.local
or
track.hash not in self.all_tracks
or
not self.all_tracks[track.hash].local
):
self.all_tracks[track.hash] = track
self.all_tracks_sh[short_hash] = track
if (track.local):
self.local_tracks[track.hash] = track
self.update_tracks()
def add_local_tracks(self):
'''
Scan the content folder and add local tracks.
This only scans the top level folder. No subdirectories.
'''
# Create a bogus track to fix the issue
# where the screen clears itself
# This trigger the delayed import of CLI
t = Track('', '', 0, '', b'', '')
self.cli.log(f"Checking directory '{constant.FILE_PREFIX}' for media... ")
# Only check 1 level deep
tracks = []
files = os.listdir(constant.FILE_PREFIX)
for file in files:
if (os.path.isfile(os.path.join(constant.FILE_PREFIX, file))):
self.cli.log(f"Processing '{file}'...")
tracks.append(Track.from_file(file))
self.add_tracks(tracks)
self.cli.log('Done')
def do_track_list_update(self):
'''
Query all peers for an updated track list.
'''
for peer in self.connections.values():
if (peer.is_connected()):
tracks = peer.request_track_list()
if (tracks is None):
continue
self.add_tracks(tracks)
self.update_tracks()
def restore_peers(self):
'''
Restore and connect to each peer.
'''
# Load peers
self.cli.log('Loading saved peers from disk... ')
peers = Peer.load_from_disk(cli=self.cli)
# Try to reconnect to each peer
for peer in peers:
self.peer_manipulate(peer, 'add')
self.cli.log('Done')
def peer_manipulate(self, peer, method):
'''
Add or remove peers.
'''
if (method == 'remove' and peer not in self.connections):
self.cli.log(f'Invalid command: not connected to {peer}')
# The peer coming in could be a temporary peer to attempt look up the real Peer
# The real Peer will actually have the TCP connection if one is open
# Otherwise it will indicate that it isn't connected
peer = self.connections.get(peer, peer)
# Make sure the connection is actually alive
peer.ping()
# peer.is_connected() is now REAL result of whether peer is actually connected
if (method == 'remove'):
peer.disconnect()
del self.connections[peer]
elif (method == 'add'):
if (peer.is_connected()):
self.cli.log(f'Peer {peer} is already connected')
else:
# Initiate client connection and add to connections list
success = peer.connect()
self.connections[peer] = peer
if (success):
# Get the new tracks
tracks = peer.request_track_list()
if (tracks is not None):
self.add_tracks(tracks)
self.update_peers()
def update(self):
self.update_tracks()
self.update_peers()
def update_tracks(self):
if (self.should_update):
self.cli.update_available_tracks(self.all_tracks.values())
def update_peers(self):
if (self.should_update):
self.cli.update_connected_peers(self.connections.values())
def handle_commands(self, command):
if (command in ['help', '?', '/?', 'h']):
self.cli.log('\nAvailable commands:')
self.cli.log(' peer add HOST:PORT')
self.cli.log(' peer remove HOST:PORT')
self.cli.log(' track list')
self.cli.log(' track get SHORT_HASH')
return
elif (command == 'exit' or command == 'quit'):
self.cli.log('Exiting...')
# Do some work to notify neighboring nodes that you're going down?
sys.exit()
tokens = command.split()
if (len(tokens) == 0):
return
if (tokens[0] == 'peer'):
if (len(tokens) < 3 or tokens[1] not in ['add', 'remove']):
self.cli.log('usage: peer add|remove HOST:PORT')
else:
host, port = tokens[2].split(':')
peer = Peer(self.cli, host, port)
self.peer_manipulate(peer, tokens[1])
elif (tokens[0] == 'track'):
if (len(tokens) < 2 or tokens[1] not in ['list', 'get'] or (tokens[1] == 'get' and len(tokens) < 3)):
self.cli.log('Usage:')
self.cli.log(' track list')
self.cli.log(' track get SHORT_HASH')
elif (tokens[1] == 'list'):
self.do_track_list_update()
elif (tokens[1] == 'get'):
short_hash = tokens[2]
if (len(short_hash) != constant.HASH_LEN):
self.cli.log(f'SHORT_HASH should be the {constant.HASH_LEN} character prefix to the left of the track')
return
track = self.all_tracks_sh[short_hash]
track.download(self.cli)
self.update_tracks()
else:
self.cli.log('Invalid command. Type "help" for available commands')