aboutsummaryrefslogtreecommitdiff
path: root/pblive/__init__.py
blob: 55469e1bdd705c736f00c6a9acf0ff64b8d71020 (plain)
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
239
240
241
242
243
244
245
246
247
1#    PBLive
2#    Copyright © 2017  RunasSudo (Yingtong Li)
3#
4#    This program is free software: you can redistribute it and/or modify
5#    it under the terms of the GNU Affero General Public License as published by
6#    the Free Software Foundation, either version 3 of the License, or
7#    (at your option) any later version.
8#
9#    This program is distributed in the hope that it will be useful,
10#    but WITHOUT ANY WARRANTY; without even the implied warranty of
11#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12#    GNU Affero General Public License for more details.
13#
14#    You should have received a copy of the GNU Affero General Public License
15#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
16
17import eventlet
18import flask
19import flask_socketio
20
21# idk why it doesn't work...
22try:
23	from . import data
24except ImportError:
25	import data
26
27import os, os.path
28import random
29import socket
30import sys
31import yaml
32
33eventlet.monkey_patch()
34
35app = flask.Flask('pblive')
36app.jinja_env.globals['data'] = data
37
38socketio = flask_socketio.SocketIO(app)
39
40# Get server IP address
41tmp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
42tmp_socket.connect(('8.8.8.8', 0)) # Connecting to a UDP socket sends no packets
43data.server_ip = tmp_socket.getsockname()[0]
44tmp_socket.close()
45
46# Load session data
47for f in os.listdir('data'):
48	if f.endswith('.yaml') and not f.startswith('.'):
49		session_name = f[:-5]
50		with open(os.path.join('data', f)) as fh:
51			data.sessions[session_name] = data.Session.from_dict(yaml.load(fh), session_name)
52
53@app.route('/')
54def index():
55	return flask.render_template('index.html')
56
57@app.route('/admin')
58def admin():
59	return flask.render_template('admin.html')
60
61@app.route('/session/<session_name>')
62def session(session_name):
63	return flask.render_template('session.html', session=data.sessions[session_name])
64
65@app.route('/image/<location>')
66def image(location):
67	# TODO: Relative path
68	return flask.send_from_directory(os.path.join(os.getcwd(), 'data/img'), location)
69
70@app.route('/admin/session/<session_name>')
71def admin_session(session_name):
72	return flask.render_template('admin_session.html', session=data.sessions[session_name])
73
74@app.route('/debug')
75def debug():
76	assert app.debug == False
77
78@socketio.on('join')
79def socket_join(session_name):
80	app.logger.debug('New client {} connected'.format(flask.request.sid))
81	
82	session = data.sessions[session_name]
83	user = data.User(sid=flask.request.sid, session=session)
84	data.users[flask.request.sid] = user
85	
86	# Send initial colour picker
87	flask_socketio.emit('update', flask.render_template('colour_picker.html', session=session), room=flask.request.sid)
88	flask_socketio.emit('update_left', render_sidebar(user, session), room=flask.request.sid)
89
90def render_question(user, session, question_num):
91	return flask.render_template(session.questions[question_num].template, session=session, user=user, question_num=session.question_num)
92
93def render_question_admin(session, question_num):
94	return flask.render_template(session.questions[question_num].template_admin, session=session, question_num=session.question_num)
95
96def render_sidebar(user, session):
97	return flask.render_template('users.html', session=session, user=user)
98
99@socketio.on('join_admin')
100def socket_join(session_name):
101	app.logger.debug('New admin {} connected'.format(flask.request.sid))
102	
103	session = data.sessions[session_name]
104	user = data.Admin(sid=flask.request.sid, session=session)
105	data.admins_lock.acquire()
106	data.admins[flask.request.sid] = user
107	data.admins_lock.release()
108	
109	# Send initial screen
110	flask_socketio.emit('update', render_question_admin(session, session.question_num), room=flask.request.sid)
111	flask_socketio.emit('update_left', render_sidebar(user, session), room=flask.request.sid)
112
113@socketio.on('disconnect')
114def socket_disconnect():
115	app.logger.debug('Client {} disconnected'.format(flask.request.sid))
116	
117	if flask.request.sid in data.users:
118		user = data.users[flask.request.sid]
119		
120		data.users_lock.acquire()
121		del data.users[flask.request.sid]
122		data.users_lock.release()
123		
124		# Release the colour if it's being held
125		if user.colour:
126			user.session.colours.append(user.colour)
127			
128			# Relay change
129			for _, other_user in data.iterate_users():
130				if other_user != user and other_user.session == user.session:
131					flask_socketio.emit('update_left', render_sidebar(other_user, user.session), room=other_user.sid)
132					if not other_user.colour:
133						flask_socketio.emit('update', flask.render_template('colour_picker.html', session=user.session), room=other_user.sid)
134			for _, admin in data.iterate_admins():
135				if admin.session == user.session:
136					flask_socketio.emit('update_left', render_sidebar(admin, user.session), room=admin.sid)
137
138@socketio.on('register')
139def socket_register(colour_id, colour_name):
140	user = data.users[flask.request.sid]
141	
142	if not user.colour and (colour_id, colour_name) in user.session.colours:
143		user.colour = (colour_id, colour_name)
144		user.session.colours.remove(user.colour)
145		
146		# Relay change
147		for _, other_user in data.iterate_users():
148			if other_user != user and other_user.session == user.session:
149				flask_socketio.emit('update_left', render_sidebar(other_user, user.session), room=other_user.sid)
150				if not other_user.colour:
151					flask_socketio.emit('update', flask.render_template('colour_picker.html', session=user.session), room=other_user.sid)
152		for _, admin in data.iterate_admins():
153			if admin.session == user.session:
154				flask_socketio.emit('update_left', render_sidebar(admin, user.session), room=admin.sid)
155	
156	flask_socketio.emit('update', render_question(user, user.session, user.session.question_num), room=user.sid)
157	flask_socketio.emit('update_left', render_sidebar(user, user.session), room=user.sid)
158
159@socketio.on('answer')
160def socket_answer(question_num, answer):
161	user = data.users[flask.request.sid]
162	
163	if question_num == user.session.question_num:
164		if isinstance(user.session.questions[user.session.question_num], data.SpeedQuestion):
165			if question_num in user.answers:
166				# Only one shot!
167				return
168		
169		user.answers[question_num] = answer
170		
171		if isinstance(user.session.questions[user.session.question_num], data.MCQQuestion):
172			flask_socketio.emit('update', render_question(user, user.session, user.session.question_num), room=user.sid)
173		
174		# Hurry!
175		if isinstance(user.session.questions[user.session.question_num], data.SpeedQuestion):
176			if user.session.questions[user.session.question_num].timer_thread is None:
177				user.session.questions[user.session.question_num].timer_thread = data.SpeedQuestionTimerThread(flask.copy_current_request_context(do_goto_question), user.session, user.session.question_num + 1)
178				user.session.questions[user.session.question_num].timer_thread.start()
179		
180		# Relay change
181		for _, other_user in data.iterate_users():
182			if other_user.session == user.session:
183				flask_socketio.emit('update_left', render_sidebar(other_user, user.session), room=other_user.sid)
184				if isinstance(user.session.questions[user.session.question_num], data.SpeedQuestion):
185					flask_socketio.emit('update', render_question(other_user, user.session, user.session.question_num), room=other_user.sid)
186		for _, admin in data.iterate_admins():
187			if admin.session == user.session:
188				flask_socketio.emit('update', render_question_admin(user.session, user.session.question_num), room=admin.sid)
189				flask_socketio.emit('update_left', render_sidebar(admin, user.session), room=admin.sid)
190
191@socketio.on('reveal_answers')
192def socket_reveal_answers(question_num):
193	user = data.admins[flask.request.sid]
194	
195	user.session.questions[question_num].revealed = True
196	
197	flask_socketio.emit('update', render_question_admin(user.session, user.session.question_num), room=flask.request.sid)
198
199def do_goto_question(session, question_num):
200	# Cleanup old question
201	if isinstance(session.questions[session.question_num], data.SpeedQuestion):
202		if session.questions[session.question_num].timer_thread is not None:
203			session.questions[session.question_num].timer_thread.stop()
204	
205	session.question_num = question_num
206	
207	# Do work for some questions
208	if isinstance(session.questions[question_num], data.RandomQuestion):
209		session.questions[question_num].answerer = random.choice([other_user for _, other_user in data.users.items() if other_user.session == session and other_user.colour])
210	
211	# Relay change
212	for _, other_user in data.iterate_users():
213		if other_user.session == session and other_user.colour:
214			flask_socketio.emit('update', render_question(other_user, session, session.question_num), room=other_user.sid)
215			flask_socketio.emit('update_left', render_sidebar(other_user, session), room=other_user.sid)
216	for _, admin in data.iterate_admins():
217		if admin.session == session:
218			flask_socketio.emit('update', render_question_admin(session, session.question_num), room=admin.sid)
219			flask_socketio.emit('update_left', render_sidebar(admin, session), room=admin.sid)
220
221@socketio.on('goto_question')
222def socket_goto_question(question_num):
223	user = data.admins[flask.request.sid]
224	
225	do_goto_question(user.session, question_num)
226
227@socketio.on('pass_question')
228def socket_pass_question():
229	user = data.admins[flask.request.sid] if flask.request.sid in data.admins else data.users[flask.request.sid]
230	
231	if isinstance(user.session.questions[user.session.question_num], data.RandomQuestion):
232		# Re-randomise answerer
233		user.session.questions[user.session.question_num].answerer = random.choice([other_user for _, other_user in data.users.items() if other_user.session == user.session and other_user.colour])
234		
235		# Relay change
236		for _, other_user in data.iterate_users():
237			if other_user.session == user.session and other_user.colour:
238				flask_socketio.emit('update', render_question(other_user, other_user.session, other_user.session.question_num), room=other_user.sid)
239				flask_socketio.emit('update_left', render_sidebar(other_user, user.session), room=other_user.sid)
240		for _, admin in data.iterate_admins():
241			if admin.session == user.session:
242				flask_socketio.emit('update', render_question_admin(admin.session, admin.session.question_num), room=admin.sid)
243				flask_socketio.emit('update_left', render_sidebar(admin, user.session), room=admin.sid)
244
245# Start server
246if __name__ == '__main__':
247	socketio.run(app, host='0.0.0.0')
Contact (issues, pull requests, etc.) at git@yingtongli.me. Generated by cgit.