pep8ify code

This commit is contained in:
ABelliqueux 2022-11-05 20:04:27 +01:00
parent be15214a95
commit 5c03ebe2cf
4 changed files with 173 additions and 111 deletions

244
app.py
View File

@ -1,41 +1,45 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*- # pilpil-client 0.1
# # abelliqueux <contact@arthus.net>
import sys, os, base64, toml
import http.client, ssl import base64
import xml.etree.ElementTree as ET
from flask import Flask, render_template, request, make_response, jsonify from flask import Flask, render_template, request, make_response, jsonify
import gettext
import http.client
import os
import socket
import ssl
import subprocess
import requests
from shutil import which
import sys
import toml
import xml.etree.ElementTree as ET
from waitress import serve from waitress import serve
app = Flask(__name__)
# l10n # l10n
import gettext
LOCALE = os.getenv('LANG', 'en') LOCALE = os.getenv('LANG', 'en')
_ = gettext.translation('template', localedir='locales', languages=[LOCALE]).gettext _ = gettext.translation('template', localedir='locales', languages=[LOCALE]).gettext
queue_msgs = [ _("No items"), app = Flask(__name__)
_("No files queued."),
]
# Load config defaults, then look for other config files
app.config.from_file("defaults.toml", load=toml.load) app.config.from_file("defaults.toml", load=toml.load)
config_locations = ["./", "~/.", "~/.config/"] config_locations = ["./", "~/.", "~/.config/"]
for location in config_locations: for location in config_locations:
# Optional config files, ~ is expanded to $HOME on *nix, %USERPROFILE% on windows # Optional config files, ~ is expanded to $HOME on *nix, %USERPROFILE% on windows
# ~ app.config.from_file("videopi.toml", load=toml.load, silent=True)
if app.config.from_file(os.path.expanduser( location + "pilpil-server.toml"), load=toml.load, silent=True): if app.config.from_file(os.path.expanduser( location + "pilpil-server.toml"), load=toml.load, silent=True):
print( _("Found configuration file in {}").format( os.path.expanduser(location) ) ) print( _("Found configuration file in {}").format( os.path.expanduser(location) ) )
# ~ app.config.from_file(os.path.expanduser("~/.config/videopi.toml"), load=toml.load, silent=True)
### ###
hosts_available, hosts_unavailable = [],[] hosts_available, hosts_unavailable = [],[]
queue_msgs = [
# Map vlc cmds _("No items"),
# See https://github.com/videolan/vlc/blob/1336447566c0190c42a1926464fa1ad2e59adc4f/share/lua/http/requests/README.txt _("No files queued.")
]
cmd_player = { cmd_player = {
# Map vlc http url parameters to pilpil-server urls
# See https://github.com/videolan/vlc/blob/1336447566c0190c42a1926464fa1ad2e59adc4f/share/lua/http/requests/README.txt
"play" : "pl_play", "play" : "pl_play",
"resume" : "pl_forceresume", "resume" : "pl_forceresume",
"pause" : "pl_forcepause", "pause" : "pl_forcepause",
@ -62,19 +66,17 @@ cmd_player = {
#"command" : "?command=<cmd>", #"command" : "?command=<cmd>",
#"key" : "key=", #"key" : "key=",
#"browse" : "browse.xml?uri=file://~" #"browse" : "browse.xml?uri=file://~"
# System commands : }
# ~ "rssi" : "rssi", cmd_server = [
# ~ "blink" : "blink", # Map pilpil-client http url parameters to pilpil-server urls
# ~ "poweroff" : "poweroff", "blink",
# ~ "reboot" : "reboot", "reboot",
"poweroff",
"rssi"
]
} # Configuration
debug = app.config['DEFAULT']['debug']
cmd_server = ["blink", "reboot", "poweroff", "rssi"]
# Set configuration
DEBUG = app.config['DEFAULT']['DEBUG']
media_folder_remote = app.config['DEFAULT']['media_folder_remote'] media_folder_remote = app.config['DEFAULT']['media_folder_remote']
media_folder_local = os.path.expanduser(app.config['DEFAULT']['media_folder_local']) media_folder_local = os.path.expanduser(app.config['DEFAULT']['media_folder_local'])
media_exts = app.config['DEFAULT']['media_exts'] media_exts = app.config['DEFAULT']['media_exts']
@ -86,53 +88,93 @@ cmd_port = app.config['DEFAULT']['cmd_port']
useSSL = app.config['DEFAULT']['useSSL'] useSSL = app.config['DEFAULT']['useSSL']
CAfile = app.config['DEFAULT']['CAfile'] CAfile = app.config['DEFAULT']['CAfile']
sync_facility = app.config['DEFAULT']['sync_facility'] sync_facility = app.config['DEFAULT']['sync_facility']
headers = {"Authorization":"Basic " + auth} headers = {"Authorization":"Basic " + auth}
# SSl context creation should be out of class
sslcontext = ssl.create_default_context()
if os.path.exists(CAfile):
sslcontext.load_verify_locations(cafile=CAfile)
else:
sslcontext.check_hostname = False
sslcontext.verify_mode = ssl.CERT_NONE
class PilpilClient:
def __init__(self):
pass
def __init_connection(self):
pass
def __send_request(self):
pass
def isup(self):
pass
def rssi(self):
pass
def status(self):
pass
def playlist(self):
pass
def command(self, cmd):
pass
def __str__(self):
pass
def __del__(self):
pass
# Network/link utilities # Network/link utilities
# https://www.metageek.com/training/resources/understanding-rssi/ # https://www.metageek.com/training/resources/understanding-rssi/
# Rewrite using HTTPConnection
def isup(host_l, port): def isup(listed_host, port):
global DEBUG '''
import socket Check listed_host is up and listening on port by opening a socket
Return 1 if successfull.
'''
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
if useSSL: if useSSL:
sslcontext = ssl.create_default_context() # ~ sslcontext = ssl.create_default_context()
# ~ if os.path.exists(CAfile): # Disable cert validation, we just want to know if host is live
# ~ sslcontext.load_verify_locations(cafile=CAfile)
# ~ else:
# Dont validate cert, we juts want to see if host is live
sslcontext.check_hostname = False sslcontext.check_hostname = False
sslcontext.verify_mode = ssl.CERT_NONE sslcontext.verify_mode = ssl.CERT_NONE
s = sslcontext.wrap_socket(s, server_hostname=host_l) s = sslcontext.wrap_socket(s, server_hostname=listed_host)
try: try:
s.settimeout(3.0) s.settimeout(3.0)
s.connect((host_l, port)) s.connect((listed_host, port))
if DEBUG: if debug:
print( _("Port {} reachable").format(str(port)) ) print(_("Port {} reachable on {}").format(str(port), str(listed_host)))
return 1 return 1
except (socket.error, socket.timeout) as e: except (socket.error, socket.timeout) as e:
if DEBUG: if debug:
print( _("Error on connection to {} : {} : {} ").format(host_l, str(port), e) ) print(_("Error on connection to {} : {} : {} ").format(listed_host, str(port), e))
return 0 return 0
finally: finally:
s.close() s.close()
def checkHosts(host_l): def check_hosts(host_list):
hostdown, hostup = [], [] '''
hosts_number = str(len(host_l)) Check hosts in a host list are up and build then return two lists with up/down hosts.
for lhost in host_l: '''
if not isup(lhost, port): hosts_up, hosts_down = [], []
hostdown.append(lhost) hosts_number = str(len(host_list))
for local_host in host_list:
if not isup(local_host, port):
hosts_down.append(local_host)
else: else:
hostup.append(lhost) hosts_up.append(local_host)
if DEBUG: if debug:
print( _("{} of {} hosts found.").format(str(len(hostup)), hosts_number)) print( _("{} of {} hosts found.").format(str(len(hosts_up)), hosts_number))
return hostup, hostdown return hosts_up, hosts_down
# File utilities # File utilities
def listMediaFiles(folder): def list_media_files(folder):
'''
List files in folder which extension is allowed (exists in media_exts).
'''
if os.path.exists(folder): if os.path.exists(folder):
files = os.listdir(folder); files = os.listdir(folder);
medias = [] medias = []
@ -144,23 +186,28 @@ def listMediaFiles(folder):
else: else:
return [] return []
def httpUpload(filename, hostl, trailing_slash=1): def HTTP_upload(filename, host_local, port, trailing_slash=1):
import requests '''
url = "https://" + hostl + ":" + str(cmd_port) + "/upload" Build HTTP file upload request and send it.
'''
url = "https://" + host_local + ":" + str(port) + "/upload"
if not trailing_slash: if not trailing_slash:
filename = "/" + filename filename = "/" + filename
files = { "file":( filename, open( media_folder_local + filename, "rb"), "multipart/form-data") } files = { "file":( filename, open( media_folder_local + filename, "rb"), "multipart/form-data") }
if DEBUG: if debug:
print(files) print(files)
resp = requests.post(url, files=files, headers=headers, verify=CAfile) resp = requests.post(url, files=files, headers=headers, verify=CAfile)
if DEBUG: if debug:
print(resp.text) print(resp.text)
if resp.ok: if resp.ok:
return 1 return 1
else: else:
return 0 return 0
def syncMediaFolder(media_folder_local, media_folder_remote, hostl, sync_facility=sync_facility): def sync_media_folder(media_folder_local, media_folder_remote, host_local, port, sync_facility=sync_facility):
'''
Sync media_folder_local with media_folder_remote using sync_facility
'''
trailing_slash = 1 trailing_slash = 1
# Check for trailing / and add it if missing # Check for trailing / and add it if missing
if media_folder_local[-1:] != "/": if media_folder_local[-1:] != "/":
@ -168,30 +215,29 @@ def syncMediaFolder(media_folder_local, media_folder_remote, hostl, sync_facilit
trailing_slash = 0 trailing_slash = 0
if media_folder_remote[-1:] != "/": if media_folder_remote[-1:] != "/":
media_folder_remote += "/" media_folder_remote += "/"
#Using http_upload
if sync_facility == "http": if sync_facility == "http":
media_list = listMediaFiles(media_folder_local) media_list = list_media_files(media_folder_local)
transfer_ok = 0 transfer_ok = 0
for media in media_list: for media in media_list:
transfer_ok += httpUpload(media, hostl, trailing_slash) transfer_ok += HTTP_upload(media, host_local, port, trailing_slash)
return _("{} files uploaded.").format(str(transfer_ok)) return _("{} files uploaded.").format(str(transfer_ok))
# Check sync utility exists
elif which(sync_facility): elif which(sync_facility):
from shutil import whichdb # Build subprocess arg list accroding to sync_facility
# Build subprocess arg list accroding to facility # Using Rsync
if sync_facility == "rsync": if sync_facility == "rsync":
scrape_str = "total size is " scrape_str = "total size is "
sync_args = [sync_facility, "-zharm", "--include='*/'"] sync_args = [sync_facility, "-zharm", "--include='*/'"]
for media_type in media_exts: for media_type in media_exts:
sync_args.append( "--include='*." + media_type + "'" ) sync_args.append( "--include='*." + media_type + "'" )
sync_args.extend(["--exclude='*'", media_folder_local, hostl + ":" + media_folder_remote]) sync_args.extend(["--exclude='*'", media_folder_local, host_local + ":" + media_folder_remote])
# Using scp
if sync_facility == "scp": if sync_facility == "scp":
media_list = listMediaFiles(media_folder_local) media_list = list_media_files(media_folder_local)
sync_args = [sync_facility, "-Crp", "-o IdentitiesOnly=yes"] sync_args = [sync_facility, "-Crp", "-o IdentitiesOnly=yes"]
for media in media_list: for media in media_list:
sync_args.append( media_folder_local + media ) sync_args.append( media_folder_local + media )
sync_args.append( hostl + ":" + media_folder_remote ) sync_args.append( host_local + ":" + media_folder_remote )
sync_proc = subprocess.run( sync_args , capture_output=True) sync_proc = subprocess.run( sync_args , capture_output=True)
if len(sync_proc.stdout): if len(sync_proc.stdout):
scrape_index = str(sync_proc.stdout).index(scrape_str)+len(scrape_str) scrape_index = str(sync_proc.stdout).index(scrape_str)+len(scrape_str)
@ -201,7 +247,10 @@ def syncMediaFolder(media_folder_local, media_folder_remote, hostl, sync_facilit
return total_size return total_size
# /requests/status.xml?command=in_enqueue&input=file:///home/pi/tst1.mp4 # /requests/status.xml?command=in_enqueue&input=file:///home/pi/tst1.mp4
def writeM3U(m3u_content : str, host : str): def write_M3U(m3u_content : str, host : str):
'''
Write a M3U file named host.m3u from m3u_content
'''
filename = host.replace(".", "_") + ".m3u" filename = host.replace(".", "_") + ".m3u"
fd = open(filename, "w") fd = open(filename, "w")
fd.write(m3u_content) fd.write(m3u_content)
@ -210,15 +259,24 @@ def writeM3U(m3u_content : str, host : str):
# String utilities # String utilities
def escapeStr(uri): def escape_str(uri):
'''
Replace spaces with %20 for http urls
'''
return uri.replace(" ", "%20") return uri.replace(" ", "%20")
def sec2min(duration): def sec2min(duration):
'''
Convert seconds to min:sec format.
'''
return('%02d:%02d' % (duration / 60, duration % 60)) return('%02d:%02d' % (duration / 60, duration % 60))
# VLC lua utilities # VLC lua utilities
# TODO : make that a class
def sendCommand(host, arg0, arg1, arg2): def send_command(host, arg0, arg1, arg2):
'''
Build a http request according to args, send it and return parsed result.
'''
portl = port portl = port
# Build request # Build request
req = "/requests/status.xml" req = "/requests/status.xml"
@ -235,19 +293,19 @@ def sendCommand(host, arg0, arg1, arg2):
if (arg0 == "sort") or (arg0 == "move") : if (arg0 == "sort") or (arg0 == "move") :
# val possible values : id, title, title nodes first, artist, genre, random, duration, title numeric, album # val possible values : id, title, title nodes first, artist, genre, random, duration, title numeric, album
# https://github.com/videolan/vlc/blob/3.0.17.4/modules/lua/libs/playlist.c#L353-L362 # https://github.com/videolan/vlc/blob/3.0.17.4/modules/lua/libs/playlist.c#L353-L362
req = req + "&val=" + escapeStr(arg2) req = req + "&val=" + escape_str(arg2)
elif arg0 == "seek" : elif arg0 == "seek" :
req = req + "&val=" + arg1 req = req + "&val=" + arg1
elif (arg0 == "enqueue") or (arg0 == "add") : elif (arg0 == "enqueue") or (arg0 == "add") :
req = req + "&input=file://" + media_folder_remote + "/" + arg1 req = req + "&input=file://" + media_folder_remote + "/" + arg1
# Send request # Send request
if useSSL: if useSSL:
sslcontext = ssl.create_default_context() # ~ sslcontext = ssl.create_default_context()
if os.path.exists(CAfile): # ~ if os.path.exists(CAfile):
sslcontext.load_verify_locations(cafile=CAfile) # ~ sslcontext.load_verify_locations(cafile=CAfile)
else: # ~ else:
sslcontext.check_hostname = False # ~ sslcontext.check_hostname = False
sslcontext.verify_mode = ssl.CERT_NONE # ~ sslcontext.verify_mode = ssl.CERT_NONE
conn = http.client.HTTPSConnection( host + ":" + str(portl), timeout=3, context = sslcontext ) conn = http.client.HTTPSConnection( host + ":" + str(portl), timeout=3, context = sslcontext )
else: else:
conn = http.client.HTTPConnection( host + ":" + str(portl), timeout=3 ) conn = http.client.HTTPConnection( host + ":" + str(portl), timeout=3 )
@ -266,7 +324,7 @@ def sendCommand(host, arg0, arg1, arg2):
# ~ data = resp.read() # ~ data = resp.read()
if arg0 == "rssi": if arg0 == "rssi":
if DEBUG: if debug:
print(data) print(data)
response_dict = { response_dict = {
'host': host, 'host': host,
@ -339,9 +397,7 @@ status_message = _("Idle")
@app.route("/") @app.route("/")
def main(): def main():
global hosts
status_message = _("Searching network for live hosts...") status_message = _("Searching network for live hosts...")
# ~ hosts_available, hosts_unavailable = checkHosts(hosts)
templateData = { templateData = {
'hosts' : hosts, 'hosts' : hosts,
'status_message' : status_message, 'status_message' : status_message,
@ -352,13 +408,13 @@ def main():
@app.route("/scan") @app.route("/scan")
def scan(): def scan():
global hosts_available, hosts_unavailable global hosts_available, hosts_unavailable
hosts_available, hosts_unavailable = checkHosts(hosts) hosts_available, hosts_unavailable = check_hosts(hosts)
hosts_status = [hosts_available, hosts_unavailable] hosts_status = [hosts_available, hosts_unavailable]
return hosts_status return hosts_status
@app.route("/browse") @app.route("/browse")
def browse(): def browse():
files = listMediaFiles(media_folder_local) files = list_media_files(media_folder_local)
return files; return files;
@ -366,9 +422,9 @@ def browse():
def sync(host): def sync(host):
if host == "all": if host == "all":
for hostl in hosts_available: for hostl in hosts_available:
size = syncMediaFolder(media_folder_local, media_folder_remote, hostl) size = sync_media_folder(media_folder_local, media_folder_remote, hostl, cmd_port)
else: else:
size = syncMediaFolder(media_folder_local, media_folder_remote, host) size = sync_media_folder(media_folder_local, media_folder_remote, host, cmd_port)
return size; return size;
@ -382,13 +438,13 @@ def action(host, arg0, arg1, arg2):
elif host == "all": elif host == "all":
resp = [] resp = []
for hostl in hosts_available: for hostl in hosts_available:
resp.append( sendCommand(hostl, arg0, arg1, arg2) ) resp.append( send_command(hostl, arg0, arg1, arg2) )
status_message = resp status_message = resp
elif host not in hosts_available: elif host not in hosts_available:
status_message = "<p>{}</p>".format("Host is not reachable") status_message = "<p>{}</p>".format("Host is not reachable")
else: else:
status_message = sendCommand(host, arg0, arg1, arg2) status_message = send_command(host, arg0, arg1, arg2)
if DEBUG: if debug:
print(status_message) print(status_message)
return status_message return status_message

View File

@ -42,9 +42,7 @@ sha256 : ec3e17fc9b41f8c5181484e9866be2d1d92cab8403210e3d22f4f689edd4cfde
md5 : md5 :
sha256 : sha256 :
* Add rt8821cu driver back
* Use safe overclocking settings for rpi1, 3, 4, better memory split
* Add blink function to pilpil
# FS checklist # FS checklist
* /etc/dhcpcd.conf * /etc/dhcpcd.conf
@ -59,20 +57,28 @@ sha256 :
# DOING NEXT : # DOING NEXT :
* ~ Test with several rpis
* Define http auth secret at setup
# DONE : # DONE :
* Increase live host scan when host first found * webgui : Increase live host scan when host first found
* webgui : Btn hover/press fixed
* server : Disable ssl verif for isup()
* server : Add blink command
* client : rt8821cu driver back
* client : Use safe overclocking settings for rpi1, 3, 4, better memory split
* setup : Split git repo from pilpil-server
* setup : Generate/install http auth secret at setup
* setup : Add dry, help flags
* FR localisation for server, client and install script
# OTHER: # OTHER:
* get_client_rssi.sh on server * get_client_rssi.sh on server
# TODO : # TODO :
* FR localisation
* GUI : Btn hover/press ?
* ? python sanitization
* ~ Test with several rpis
* ? Scripts hotspot linux/win/mac * ? Scripts hotspot linux/win/mac
* ? Config sync * ? Config sync
* ! Remove git personal details/resolv.conf, remove authorized_keys, ssh config, clean home, re-enable ssh pw login * ! Remove git personal details/resolv.conf, remove authorized_keys, ssh config, clean home, re-enable ssh pw login

View File

@ -1,5 +1,5 @@
[DEFAULT] [DEFAULT]
DEBUG = 0 debug = 0
useSSL = false useSSL = false
CAfile = "selfCA.crt" CAfile = "selfCA.crt"
# scp, rsync, http # scp, rsync, http

View File

@ -1,5 +1,5 @@
[DEFAULT] [DEFAULT]
DEBUG = 0 debug = 0
useSSL = true useSSL = true
CAfile = "selfCA.crt" CAfile = "selfCA.crt"
# scp, rsync, http # scp, rsync, http