deemix-gui-pyweb/server.py
2020-09-15 16:44:40 +01:00

418 lines
13 KiB
Python

#!/usr/bin/env python3
import logging
import signal
import sys
import subprocess
from os import path
import json
import requests
from urllib.request import urlopen
from datetime import datetime
from flask import Flask, render_template, request, session, redirect, copy_current_request_context
from flask_socketio import SocketIO, emit
from werkzeug.middleware.proxy_fix import ProxyFix
from deemix import __version__ as deemixVersion
from app import deemix
from deemix.api.deezer import Deezer
from deemix.app.messageinterface import MessageInterface
# Workaround for MIME type error in certain Windows installs
# https://github.com/pallets/flask/issues/1045#issuecomment-42202749
import mimetypes
mimetypes.add_type('text/css', '.css')
mimetypes.add_type('text/javascript', '.js')
# Makes engineio accept more packets from client, needed for long URL lists in addToQueue requests
# https://github.com/miguelgrinberg/python-engineio/issues/142#issuecomment-545807047
from engineio.payload import Payload
Payload.max_decode_packets = 500
app = None
gui = None
arl = None
class CustomFlask(Flask):
jinja_options = Flask.jinja_options.copy()
jinja_options.update(dict(
block_start_string='$$',
block_end_string='$$',
variable_start_string='$',
variable_end_string='$',
comment_start_string='$#',
comment_end_string='#$',
))
def resource_path(relative_path):
""" Get absolute path to resource, works for dev and for PyInstaller """
try:
# PyInstaller creates a temp folder and stores path in _MEIPASS
base_path = sys._MEIPASS
except Exception:
base_path = path.dirname(path.abspath(path.realpath(__file__)))
return path.join(base_path, relative_path)
gui_dir = resource_path(path.join('webui', 'public'))
if not path.exists(gui_dir):
gui_dir = resource_path('webui')
if not path.isfile(path.join(gui_dir, 'index.html')):
sys.exit("WebUI not found, please download and add a WebUI")
server = CustomFlask(__name__, static_folder=gui_dir, template_folder=gui_dir, static_url_path="")
server.config['SEND_FILE_MAX_AGE_DEFAULT'] = 1 # disable caching
socketio = SocketIO(server)
server.wsgi_app = ProxyFix(server.wsgi_app, x_for=1, x_proto=1)
class SocketInterface(MessageInterface):
def send(self, message, value=None):
if value:
socketio.emit(message, value)
else:
socketio.emit(message)
socket_interface = SocketInterface()
loginWindow = False
serverLog = logging.getLogger('werkzeug')
serverLog.disabled = True
logging.getLogger('socketio').setLevel(logging.ERROR)
logging.getLogger('engineio').setLevel(logging.ERROR)
#server.logger.disabled = True
firstConnection = True
currentVersion = None
latestVersion = None
updateAvailable = False
def compare_versions(currentVersion, latestVersion):
if not latestVersion or not currentVersion:
return False
(currentDate, currentCommit) = tuple(currentVersion.split('-'))
(latestDate, latestCommit) = tuple(latestVersion.split('-'))
currentDate = currentDate.split('.')
latestDate = latestDate.split('.')
current = datetime(int(currentDate[0]), int(currentDate[1]), int(currentDate[2]))
latest = datetime(int(latestDate[0]), int(latestDate[1]), int(latestDate[2]))
if latest > current:
return True
elif latest == current:
return latestCommit != currentCommit
else:
return False
def check_for_updates():
global currentVersion, latestVersion, updateAvailable
commitFile = resource_path('version.txt')
if path.isfile(commitFile):
print("Checking for updates...")
with open(commitFile, 'r') as f:
currentVersion = f.read().strip()
try:
latestVersion = requests.get("https://deemix.app/pyweb/latest")
latestVersion.raise_for_status()
latestVersion = latestVersion.text.strip()
except:
latestVersion = None
if currentVersion and latestVersion:
updateAvailable = compare_versions(currentVersion, latestVersion)
if updateAvailable:
print("Update available! Commit: "+latestVersion)
else:
print("You're running the latest version")
def shutdown(interface=None):
if app is not None:
app.shutdown(interface=interface)
socketio.stop()
def shutdown_handler(signalnum, frame):
shutdown()
@server.route('/')
def landing():
return render_template('index.html')
@server.errorhandler(404)
def not_found_handler(e):
return redirect("/")
@server.route('/shutdown')
def closing():
shutdown(interface=socket_interface)
return 'Server Closed'
serverwide_arl = "--serverwide-arl" in sys.argv
if serverwide_arl:
print("Server-wide ARL enabled.")
@socketio.on('connect')
def on_connect():
session['dz'] = Deezer()
settings = app.getSettings()
spotifyCredentials = app.getSpotifyCredentials()
defaultSettings = app.getDefaultSettings()
emit('init_settings', (settings, spotifyCredentials, defaultSettings))
emit('init_update',
{'currentCommit': currentVersion,
'latestCommit': latestVersion,
'updateAvailable': updateAvailable,
'deemixVersion': deemixVersion}
)
if serverwide_arl:
login(arl)
else:
emit('init_autologin')
queue, queueComplete, queueList, currentItem = app.initDownloadQueue()
if len(queueList.keys()):
emit('init_downloadQueue',{
'queue': queue,
'queueComplete': queueComplete,
'queueList': queueList,
'currentItem': currentItem
})
emit('init_home', session['dz'].get_charts())
emit('init_charts', app.get_charts(session['dz']))
@socketio.on('get_home_data')
def get_home_data():
emit('init_home', app.get_home(session['dz']))
@socketio.on('get_charts_data')
def get_charts_data():
emit('init_charts', app.get_charts(session['dz']))
@socketio.on('login')
def login(arl, force=False, child=0):
global firstConnection
if child == None:
child = 0
arl = arl.strip()
emit('logging_in')
if not session['dz'].logged_in:
result = session['dz'].login_via_arl(arl, int(child))
else:
if force:
session['dz'] = Deezer()
result = session['dz'].login_via_arl(arl, int(child))
if result == 1:
result = 3
else:
result = 2
emit('logged_in', {'status': result, 'arl': arl, 'user': session['dz'].user})
if firstConnection and result in [1, 3]:
firstConnection = False
app.restoreDownloadQueue(session['dz'], socket_interface)
if result != 0:
emit('familyAccounts', session['dz'].childs)
emit('init_favorites', app.getUserFavorites(session['dz']))
@socketio.on('changeAccount')
def changeAccount(child):
emit('accountChanged', session['dz'].change_account(int(child)))
emit('init_favorites', app.getUserFavorites(session['dz']))
@socketio.on('logout')
def logout():
status = 0
if session['dz'].logged_in:
session['dz'] = Deezer()
status = 0
else:
status = 1
emit('logged_out', status)
@socketio.on('mainSearch')
def mainSearch(data):
if data['term'].strip() != "":
result = app.mainSearch(session['dz'], data['term'])
result['ack'] = data.get('ack')
emit('mainSearch', result)
@socketio.on('search')
def search(data):
if data['term'].strip() != "":
result = app.search(session['dz'], data['term'], data['type'], data['start'], data['nb'])
result['type'] = data['type']
result['ack'] = data.get('ack')
emit('search', result)
@socketio.on('queueRestored')
def queueRestored():
app.queueRestored(session['dz'], socket_interface)
@socketio.on('addToQueue')
def addToQueue(data):
result = app.addToQueue(session['dz'], data['url'], data['bitrate'], interface=socket_interface, ack=data.get('ack'))
if result == "Not logged in":
emit('loginNeededToDownload')
@socketio.on('removeFromQueue')
def removeFromQueue(uuid):
app.removeFromQueue(uuid, interface=socket_interface)
@socketio.on('removeFinishedDownloads')
def removeFinishedDownloads():
app.removeFinishedDownloads(interface=socket_interface)
@socketio.on('cancelAllDownloads')
def cancelAllDownloads():
app.cancelAllDownloads(interface=socket_interface)
@socketio.on('saveSettings')
def saveSettings(settings, spotifyCredentials, spotifyUser):
app.saveSettings(settings)
app.setSpotifyCredentials(spotifyCredentials)
socketio.emit('updateSettings', (settings, spotifyCredentials))
if spotifyUser != False:
emit('updated_userSpotifyPlaylists', app.updateUserSpotifyPlaylists(spotifyUser))
@socketio.on('getTracklist')
def getTracklist(data):
if data['type'] == 'artist':
artistAPI = session['dz'].get_artist(data['id'])
artistAPI['releases'] = session['dz'].get_artist_discography_gw(data['id'], 100)
emit('show_artist', artistAPI)
elif data['type'] == 'spotifyplaylist':
playlistAPI = app.getSpotifyPlaylistTracklist(data['id'])
for i in range(len(playlistAPI['tracks'])):
playlistAPI['tracks'][i] = playlistAPI['tracks'][i]['track']
playlistAPI['tracks'][i]['selected'] = False
emit('show_spotifyplaylist', playlistAPI)
else:
releaseAPI = getattr(session['dz'], 'get_' + data['type'])(data['id'])
releaseTracksAPI = getattr(session['dz'], 'get_' + data['type'] + '_tracks')(data['id'])['data']
tracks = []
showdiscs = False
if data['type'] == 'album' and len(releaseTracksAPI) and releaseTracksAPI[-1]['disk_number'] != 1:
current_disk = 0
showdiscs = True
for track in releaseTracksAPI:
if showdiscs and int(track['disk_number']) != current_disk:
current_disk = int(track['disk_number'])
tracks.append({'type': 'disc_separator', 'number': current_disk})
track['selected'] = False
tracks.append(track)
releaseAPI['tracks'] = tracks
emit('show_' + data['type'], releaseAPI)
@socketio.on('analyzeLink')
def analyzeLink(link):
if 'deezer.page.link' in link:
link = urlopen(link).url
(type, data) = app.analyzeLink(session['dz'], link)
if len(data):
emit('analyze_'+type, data)
else:
emit('analyze_notSupported')
@socketio.on('getChartTracks')
def getChartTracks(id):
emit('setChartTracks', session['dz'].get_playlist_tracks(id)['data'])
@socketio.on('update_userFavorites')
def update_userFavorites():
emit('updated_userFavorites', app.getUserFavorites(session['dz']))
@socketio.on('update_userSpotifyPlaylists')
def update_userSpotifyPlaylists(spotifyUser):
if spotifyUser != False:
emit('updated_userSpotifyPlaylists', app.updateUserSpotifyPlaylists(spotifyUser))
@socketio.on('update_userPlaylists')
def update_userPlaylists():
emit('updated_userPlaylists', app.updateUserPlaylists(session['dz']))
@socketio.on('update_userAlbums')
def update_userAlbums():
emit('updated_userAlbums', app.updateUserAlbums(session['dz']))
@socketio.on('update_userArtists')
def update_userArtists():
emit('updated_userArtists', app.updateUserArtists(session['dz']))
@socketio.on('update_userTracks')
def update_userTracks():
emit('updated_userTracks', app.updateUserTracks(session['dz']))
@socketio.on('openDownloadsFolder')
def openDownloadsFolder():
folder = app.getDownloadFolder()
if sys.platform == 'darwin':
subprocess.check_call(['open', folder])
elif sys.platform == 'linux':
subprocess.check_call(['xdg-open', folder])
elif sys.platform == 'win32':
subprocess.check_call(['explorer', folder])
@socketio.on('selectDownloadFolder')
def selectDownloadFolder():
if gui:
gui.selectDownloadFolder_trigger.emit()
gui._selectDownloadFolder_semaphore.acquire()
result = gui.downloadFolder
if result:
emit('downloadFolderSelected', result)
else:
print("Can't open folder selection, you're not running the gui")
@socketio.on('applogin')
def applogin():
if gui:
if not session['dz'].logged_in:
gui.appLogin_trigger.emit()
gui._appLogin_semaphore.acquire()
if gui.arl:
emit('applogin_arl', gui.arl)
gui.arl = None
else:
emit('logged_in', {'status': 2, 'user': session['dz'].user})
else:
print("Can't open login page, you're not running the gui")
def run_server(port, host="127.0.0.1", portable=None, mainWindow=None):
global app, gui, arl
app = deemix(portable)
gui = mainWindow
if serverwide_arl:
arl = app.getConfigArl()
check_for_updates()
print("Starting server at http://" + host + ":" + str(port))
socketio.run(server, host=host, port=port)
if __name__ == '__main__':
port = 6595
host = "127.0.0.1"
portable = None
if len(sys.argv) >= 2:
try:
port = int(sys.argv[1])
except ValueError:
pass
if '--portable' in sys.argv:
portable = path.join(path.dirname(path.realpath(__file__)), 'config')
if '--host' in sys.argv:
host = str(sys.argv[sys.argv.index("--host")+1])
signal.signal(signal.SIGINT, shutdown_handler)
signal.signal(signal.SIGTERM, shutdown_handler)
run_server(port, host, portable)