forked from flesniak/python-prodj-link
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathprodj.py
155 lines (138 loc) · 5.64 KB
/
prodj.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
import socket
import logging
from threading import Thread
from select import select
from packets_dump import *
from enum import Enum
import packets
from clientlist import ClientList
from dataprovider import DataProvider
from vcdj import Vcdj
from nfsclient import NfsClient
from ip import guess_own_iface
class OwnIpStatus(Enum):
notNeeded = 1,
waiting = 2,
acquired = 3
class ProDj(Thread):
def __init__(self):
super().__init__()
self.cl = ClientList(self)
self.data = DataProvider(self)
self.vcdj = Vcdj(self)
self.nfs = NfsClient(self)
self.keepalive_ip = "0.0.0.0"
self.keepalive_port = 50000
self.beat_ip = "0.0.0.0"
self.beat_port = 50001
self.status_ip = "0.0.0.0"
self.status_port = 50002
self.need_own_ip = OwnIpStatus.notNeeded
self.own_ip = None
def start(self):
self.keepalive_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.keepalive_sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
self.keepalive_sock.bind((self.keepalive_ip, self.keepalive_port))
logging.info("Listening on {}:{} for keepalive packets".format(self.keepalive_ip, self.keepalive_port))
self.beat_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.beat_sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
self.beat_sock.bind((self.beat_ip, self.beat_port))
logging.info("Listening on {}:{} for beat packets".format(self.beat_ip, self.beat_port))
self.status_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.status_sock.bind((self.status_ip, self.status_port))
logging.info("Listening on {}:{} for status packets".format(self.status_ip, self.status_port))
self.socks = [self.keepalive_sock, self.beat_sock, self.status_sock]
self.keep_running = True
self.data.start()
super().start()
def stop(self):
self.keep_running = False
self.nfs.stop()
self.data.stop()
self.vcdj_disable()
self.join()
self.keepalive_sock.close()
self.beat_sock.close()
def vcdj_set_player_number(self, vcdj_player_number=5):
logging.info("Player number set to {}".format(vcdj_player_number))
self.vcdj.player_number = vcdj_player_number
#self.data.dbc.own_player_number = vcdj_player_number
def vcdj_enable(self):
self.vcdj_set_iface()
self.vcdj.start()
def vcdj_disable(self):
self.vcdj.stop()
self.vcdj.join()
def vcdj_set_iface(self):
if self.own_ip is not None:
self.vcdj.set_interface_data(*self.own_ip[1:4])
def run(self):
logging.debug("ProDj: starting main loop")
while self.keep_running:
rdy = select(self.socks,[],[],1)[0]
for sock in rdy:
if sock == self.keepalive_sock:
data, addr = self.keepalive_sock.recvfrom(128)
self.handle_keepalive_packet(data, addr)
elif sock == self.beat_sock:
data, addr = self.beat_sock.recvfrom(128)
self.handle_beat_packet(data, addr)
elif sock == self.status_sock:
data, addr = self.status_sock.recvfrom(256)
self.handle_status_packet(data, addr)
self.cl.gc()
logging.debug("ProDj: main loop finished")
def handle_keepalive_packet(self, data, addr):
#logging.debug("Broadcast keepalive packet from {}".format(addr))
try:
packet = packets.KeepAlivePacket.parse(data)
except Exception as e:
logging.warning("Failed to parse keepalive packet from {}, {} bytes: {}".format(addr, len(data), e))
dump_packet_raw(data)
return
# both packet types give us enough information to store the client
if packet["type"] in ["type_ip", "type_status", "type_change"]:
self.cl.eatKeepalive(packet)
if self.own_ip is None and len(self.cl.getClientIps()) > 0:
self.own_ip = guess_own_iface(self.cl.getClientIps())
if self.own_ip is not None:
logging.info("Guessed own interface {} ip {} mask {} mac {}".format(*self.own_ip))
self.vcdj_set_iface()
dump_keepalive_packet(packet)
def handle_beat_packet(self, data, addr):
#logging.debug("Broadcast beat packet from {}".format(addr))
try:
packet = packets.BeatPacket.parse(data)
except Exception as e:
logging.warning("Failed to parse beat packet from {}, {} bytes: {}".format(addr, len(data), e))
dump_packet_raw(data)
return
if packet["type"] in ["type_beat", "type_mixer"]:
self.cl.eatBeat(packet)
dump_beat_packet(packet)
def handle_status_packet(self, data, addr):
#logging.debug("Broadcast status packet from {}".format(addr))
try:
packet = packets.StatusPacket.parse(data)
except Exception as e:
logging.warning("Failed to parse status packet from {}, {} bytes: {}".format(addr, len(data), e))
dump_packet_raw(data)
return
self.cl.eatStatus(packet)
dump_status_packet(packet)
# called whenever a keepalive packet is received
# arguments of cb: this clientlist object, player number of changed client
def set_client_keepalive_callback(self, cb=None):
self.cl.client_keepalive_callback = cb
# called whenever a status update of a known client is received
# arguments of cb: this clientlist object, player number of changed client
def set_client_change_callback(self, cb=None):
self.cl.client_change_callback = cb
# called when status update of a known master is received or the master changes
# arguments of cb: this clientlist object, player number of changed master
def set_master_change_callback(self, cb=None):
self.cl.master_change_callback = cb
# called when a player media changes
# arguments of cb: this clientlist object, player_number, changed slot
def set_media_change_callback(self, cb=None):
self.cl.media_change_callback = cb