]> git.0d.be Git - botaradio.git/blob - interface.py
fix: some small issue
[botaradio.git] / interface.py
1 #!/usr/bin/python3
2
3 from functools import wraps
4 from flask import Flask, render_template, request, redirect, send_file, Response, jsonify, abort
5 import variables as var
6 import util
7 from datetime import datetime
8 import os
9 import os.path
10 import shutil
11 import random
12 from werkzeug.utils import secure_filename
13 import errno
14 import media
15 from media.playlist import PlaylistItemWrapper
16 from media.file import FileItem
17 from media.url_from_playlist import PlaylistURLItem, get_playlist_info
18 from media.url import URLItem
19 from media.radio import RadioItem
20 import logging
21 import time
22 import constants
23
24
25 class ReverseProxied(object):
26     '''Wrap the application in this middleware and configure the
27     front-end server to add these headers, to let you quietly bind
28     this to a URL other than / and to an HTTP scheme that is
29     different than what is used locally.
30
31     In nginx:
32     location /myprefix {
33         proxy_pass http://192.168.0.1:5001;
34         proxy_set_header Host $host;
35         proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
36         proxy_set_header X-Scheme $scheme;
37         proxy_set_header X-Script-Name /myprefix;
38         }
39
40     :param app: the WSGI application
41     '''
42
43     def __init__(self, app):
44         self.app = app
45
46     def __call__(self, environ, start_response):
47         script_name = environ.get('HTTP_X_SCRIPT_NAME', '')
48         if script_name:
49             environ['SCRIPT_NAME'] = script_name
50             path_info = environ['PATH_INFO']
51             if path_info.startswith(script_name):
52                 environ['PATH_INFO'] = path_info[len(script_name):]
53
54         scheme = environ.get('HTTP_X_SCHEME', '')
55         if scheme:
56             environ['wsgi.url_scheme'] = scheme
57         real_ip = environ.get('HTTP_X_REAL_IP', '')
58         if real_ip:
59             environ['REMOTE_ADDR'] = real_ip
60         return self.app(environ, start_response)
61
62
63 web = Flask(__name__)
64 log = logging.getLogger("bot")
65 user = 'Remote Control'
66
67 def init_proxy():
68     global web
69     if var.is_proxified:
70         web.wsgi_app = ReverseProxied(web.wsgi_app)
71
72 # https://stackoverflow.com/questions/29725217/password-protect-one-webpage-in-flask-app
73
74 def check_auth(username, password):
75     """This function is called to check if a username /
76     password combination is valid.
77     """
78     return username == var.config.get("webinterface", "user") and password == var.config.get("webinterface", "password")
79
80 def authenticate():
81     """Sends a 401 response that enables basic auth"""
82     global log
83     return Response(
84     'Could not verify your access level for that URL.\n'
85     'You have to login with proper credentials', 401,
86     {'WWW-Authenticate': 'Basic realm="Login Required"'})
87
88 def requires_auth(f):
89     @wraps(f)
90     def decorated(*args, **kwargs):
91         global log
92         auth = request.authorization
93         if var.config.getboolean("webinterface", "require_auth") and (not auth or not check_auth(auth.username, auth.password)):
94             if auth:
95                 log.info("web: Failed login attempt, user: %s" % auth.username)
96             return authenticate()
97         return f(*args, **kwargs)
98     return decorated
99
100
101 @web.route("/", methods=['GET'])
102 @requires_auth
103 def index():
104     folder_path = var.music_folder
105     files = util.get_recursive_file_list_sorted(var.music_folder)
106     music_library = util.Dir(folder_path)
107     for file in files:
108         music_library.add_file(file)
109
110
111     return render_template('index.html',
112                            all_files=files,
113                            music_library=music_library,
114                            os=os,
115                            playlist=var.playlist,
116                            user=var.user,
117                            paused=var.bot.is_pause
118                            )
119
120 @web.route("/playlist", methods=['GET'])
121 @requires_auth
122 def playlist():
123     if var.playlist.length() == 0:
124         return jsonify({'items': [render_template('playlist.html',
125                                m=False,
126                                index=-1
127                                )]
128                         })
129
130     items = []
131
132     for index, item_wrapper in enumerate(var.playlist):
133          items.append(render_template('playlist.html',
134                                      index=index,
135                                      m=item_wrapper.item,
136                                      playlist=var.playlist
137                                      )
138                      )
139
140     return jsonify({ 'items': items })
141
142 def status():
143     if (var.playlist.length() > 0):
144         return jsonify({'ver': var.playlist.version,
145                         'empty': False,
146                         'play': not var.bot.is_pause,
147                         'mode': var.playlist.mode})
148     else:
149         return jsonify({'ver': var.playlist.version,
150                         'empty': True,
151                         'play': False,
152                         'mode': var.playlist.mode})
153
154
155 @web.route("/post", methods=['POST'])
156 @requires_auth
157 def post():
158     global log
159
160     folder_path = var.music_folder
161     if request.method == 'POST':
162         if request.form:
163             log.debug("web: Post request from %s: %s" % ( request.remote_addr, str(request.form)))
164         if 'add_file_bottom' in request.form and ".." not in request.form['add_file_bottom']:
165             path = var.music_folder + request.form['add_file_bottom']
166             if os.path.isfile(path):
167                 music_wrapper = PlaylistItemWrapper(FileItem(var.bot, request.form['add_file_bottom']), user)
168                 var.playlist.append(music_wrapper)
169                 log.info('web: add to playlist(bottom): ' + music_wrapper.format_debug_string())
170
171         elif 'add_file_next' in request.form and ".." not in request.form['add_file_next']:
172             path = var.music_folder + request.form['add_file_next']
173             if os.path.isfile(path):
174                 music_wrapper = PlaylistItemWrapper(FileItem(var.bot, request.form['add_file_next']), user)
175                 var.playlist.insert(var.playlist.current_index + 1, music_wrapper)
176                 log.info('web: add to playlist(next): ' + music_wrapper.format_debug_string())
177
178         elif ('add_folder' in request.form and ".." not in request.form['add_folder']) or ('add_folder_recursively' in request.form and ".." not in request.form['add_folder_recursively']):
179             try:
180                 folder = request.form['add_folder']
181             except:
182                 folder = request.form['add_folder_recursively']
183
184             if not folder.endswith('/'):
185                 folder += '/'
186
187             print('folder:', folder)
188
189             if os.path.isdir(var.music_folder + folder):
190
191                 files = util.get_recursive_file_list_sorted(var.music_folder)
192                 music_library = util.Dir(folder_path)
193                 for file in files:
194                     music_library.add_file(file)
195
196                 if 'add_folder_recursively' in request.form:
197                     files = music_library.get_files_recursively(folder)
198                 else:
199                     files = music_library.get_files(folder)
200
201                 music_wrappers = list(map(
202                     lambda file: PlaylistItemWrapper(FileItem(var.bot, file), user),
203                     files))
204
205                 var.playlist.extend(files)
206
207                 for music_wrapper in music_wrappers:
208                     log.info('web: add to playlist: ' + music_wrapper.format_debug_string())
209
210
211         elif 'add_url' in request.form:
212             music_wrapper = PlaylistItemWrapper(URLItem(var.bot, request.form['add_url']), user)
213             var.playlist.append(music_wrapper)
214
215             log.info("web: add to playlist: " + music_wrapper.format_debug_string())
216             if var.playlist.length() == 2:
217                 # If I am the second item on the playlist. (I am the next one!)
218                 var.bot.async_download_next()
219
220         elif 'add_radio' in request.form:
221             url = request.form['add_radio']
222             music_wrapper = PlaylistItemWrapper(RadioItem(var.bot, url), user)
223             var.playlist.append(music_wrapper)
224
225             log.info("cmd: add to playlist: " + music_wrapper.format_debug_string())
226
227         elif 'delete_music' in request.form:
228             music_wrapper = var.playlist[int(request.form['delete_music'])]
229             log.info("web: delete from playlist: " + music_wrapper.format_debug_string())
230
231             if var.playlist.length() >= int(request.form['delete_music']):
232                 index = int(request.form['delete_music'])
233
234                 if index == var.playlist.current_index:
235                     var.playlist.remove(index)
236
237                     if index < len(var.playlist):
238                         if not var.bot.is_pause:
239                             var.bot.interrupt_playing()
240                             var.playlist.current_index -= 1
241                             # then the bot will move to next item
242
243                     else:  # if item deleted is the last item of the queue
244                         var.playlist.current_index -= 1
245                         if not var.bot.is_pause:
246                             var.bot.interrupt_playing()
247                 else:
248                     var.playlist.remove(index)
249
250
251         elif 'play_music' in request.form:
252             music_wrapper = var.playlist[int(request.form['play_music'])]
253             log.info("web: jump to: " + music_wrapper.format_debug_string())
254
255             if len(var.playlist) >= int(request.form['play_music']):
256                 var.playlist.point_to(int(request.form['play_music']) - 1)
257                 var.bot.interrupt_playing()
258
259         elif 'delete_music_file' in request.form and ".." not in request.form['delete_music_file']:
260             path = var.music_folder + request.form['delete_music_file']
261             if os.path.isfile(path):
262                 log.info("web: delete file " + path)
263                 os.remove(path)
264
265         elif 'delete_folder' in request.form and ".." not in request.form['delete_folder']:
266             path = var.music_folder + request.form['delete_folder']
267             if os.path.isdir(path):
268                 log.info("web: delete folder " + path)
269                 shutil.rmtree(path)
270                 time.sleep(0.1)
271
272         elif 'action' in request.form:
273             action = request.form['action']
274             if action == "randomize":
275                 var.bot.interrupt_playing()
276                 var.playlist.set_mode("random")
277                 var.db.set('playlist', 'playback_mode', "random")
278                 log.info("web: playback mode changed to random.")
279             if action == "one-shot":
280                 var.playlist.set_mode("one-shot")
281                 var.db.set('playlist', 'playback_mode', "one-shot")
282                 log.info("web: playback mode changed to one-shot.")
283             if action == "repeat":
284                 var.playlist.set_mode("repeat")
285                 var.db.set('playlist', 'playback_mode', "repeat")
286                 log.info("web: playback mode changed to repeat.")
287             elif action == "stop":
288                 var.bot.stop()
289             elif action == "pause":
290                 var.bot.pause()
291             elif action == "resume":
292                 var.bot.resume()
293             elif action == "clear":
294                 var.bot.clear()
295             elif action == "volume_up":
296                 if var.bot.volume_set + 0.03 < 1.0:
297                     var.bot.volume_set = var.bot.volume_set + 0.03
298                 else:
299                     var.bot.volume_set = 1.0
300                 var.db.set('bot', 'volume', str(var.bot.volume_set))
301                 log.info("web: volume up to %d" % (var.bot.volume_set * 100))
302             elif action == "volume_down":
303                 if var.bot.volume_set - 0.03 > 0:
304                     var.bot.volume_set = var.bot.volume_set - 0.03
305                 else:
306                     var.bot.volume_set = 0
307                 var.db.set('bot', 'volume', str(var.bot.volume_set))
308                 log.info("web: volume up to %d" % (var.bot.volume_set * 100))
309
310     return status()
311
312 @web.route('/upload', methods=["POST"])
313 def upload():
314     global log
315
316     files = request.files.getlist("file[]")
317     if not files:
318         return redirect("./", code=406)
319
320     #filename = secure_filename(file.filename).strip()
321     for file in files:
322         filename = file.filename
323         if filename == '':
324             return redirect("./", code=406)
325
326         targetdir = request.form['targetdir'].strip()
327         if targetdir == '':
328             targetdir = 'uploads/'
329         elif '../' in targetdir:
330             return redirect("./", code=406)
331
332         log.info('web: Uploading file from %s:' % request.remote_addr)
333         log.info('web: - filename: ' + filename)
334         log.info('web: - targetdir: ' + targetdir)
335         log.info('web:  - mimetype: ' + file.mimetype)
336
337         if "audio" in file.mimetype:
338             storagepath = os.path.abspath(os.path.join(var.music_folder, targetdir))
339             print('storagepath:',storagepath)
340             if not storagepath.startswith(os.path.abspath(var.music_folder)):
341                 return redirect("./", code=406)
342
343             try:
344                 os.makedirs(storagepath)
345             except OSError as ee:
346                 if ee.errno != errno.EEXIST:
347                     return redirect("./", code=500)
348
349             filepath = os.path.join(storagepath, filename)
350             log.info(' - filepath: ' + filepath)
351             if os.path.exists(filepath):
352                 return redirect("./", code=406)
353
354             file.save(filepath)
355         else:
356             return redirect("./", code=409)
357
358     return redirect("./", code=302)
359
360
361 @web.route('/download', methods=["GET"])
362 def download():
363     global log
364
365     if 'file' in request.args:
366         requested_file = request.args['file']
367         log.info('web: Download of file %s requested from %s:' % (requested_file, request.remote_addr))
368         if '../' not in requested_file:
369             folder_path = var.music_folder
370             files = util.get_recursive_file_list_sorted(var.music_folder)
371
372             if requested_file in files:
373                 filepath = os.path.join(folder_path, requested_file)
374                 try:
375                     return send_file(filepath, as_attachment=True)
376                 except Exception as e:
377                     log.exception(e)
378                     abort(404)
379     elif 'directory' in request.args:
380         requested_dir = request.args['directory']
381         folder_path = var.music_folder
382         requested_dir_fullpath = os.path.abspath(os.path.join(folder_path, requested_dir)) + '/'
383         if requested_dir_fullpath.startswith(folder_path):
384             if os.path.samefile(requested_dir_fullpath, folder_path):
385                 prefix = 'all'
386             else:
387                 prefix = secure_filename(os.path.relpath(requested_dir_fullpath, folder_path))
388             zipfile = util.zipdir(requested_dir_fullpath, prefix)
389             try:
390                 return send_file(zipfile, as_attachment=True)
391             except Exception as e:
392                 log.exception(e)
393                 abort(404)
394
395     return redirect("./", code=400)
396
397
398 if __name__ == '__main__':
399     web.run(port=8181, host="127.0.0.1")