pep8ify code
This commit is contained in:
parent
be15214a95
commit
5c03ebe2cf
252
app.py
252
app.py
|
@ -1,41 +1,45 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
import sys, os, base64, toml
|
||||
import http.client, ssl
|
||||
import xml.etree.ElementTree as ET
|
||||
# pilpil-client 0.1
|
||||
# abelliqueux <contact@arthus.net>
|
||||
|
||||
import base64
|
||||
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
|
||||
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
# l10n
|
||||
import gettext
|
||||
LOCALE = os.getenv('LANG', 'en')
|
||||
_ = gettext.translation('template', localedir='locales', languages=[LOCALE]).gettext
|
||||
|
||||
queue_msgs = [ _("No items"),
|
||||
_("No files queued."),
|
||||
]
|
||||
app = Flask(__name__)
|
||||
|
||||
# Load config defaults, then look for other config files
|
||||
app.config.from_file("defaults.toml", load=toml.load)
|
||||
config_locations = ["./", "~/.", "~/.config/"]
|
||||
for location in config_locations:
|
||||
# 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):
|
||||
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 = [],[]
|
||||
|
||||
# Map vlc cmds
|
||||
# See https://github.com/videolan/vlc/blob/1336447566c0190c42a1926464fa1ad2e59adc4f/share/lua/http/requests/README.txt
|
||||
queue_msgs = [
|
||||
_("No items"),
|
||||
_("No files queued.")
|
||||
]
|
||||
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",
|
||||
"resume" : "pl_forceresume",
|
||||
"pause" : "pl_forcepause",
|
||||
|
@ -62,77 +66,115 @@ cmd_player = {
|
|||
#"command" : "?command=<cmd>",
|
||||
#"key" : "key=",
|
||||
#"browse" : "browse.xml?uri=file://~"
|
||||
# System commands :
|
||||
# ~ "rssi" : "rssi",
|
||||
# ~ "blink" : "blink",
|
||||
# ~ "poweroff" : "poweroff",
|
||||
# ~ "reboot" : "reboot",
|
||||
}
|
||||
cmd_server = [
|
||||
# Map pilpil-client http url parameters to pilpil-server urls
|
||||
"blink",
|
||||
"reboot",
|
||||
"poweroff",
|
||||
"rssi"
|
||||
]
|
||||
|
||||
}
|
||||
|
||||
cmd_server = ["blink", "reboot", "poweroff", "rssi"]
|
||||
|
||||
# Set configuration
|
||||
|
||||
DEBUG = app.config['DEFAULT']['DEBUG']
|
||||
# Configuration
|
||||
debug = app.config['DEFAULT']['debug']
|
||||
media_folder_remote = app.config['DEFAULT']['media_folder_remote']
|
||||
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']
|
||||
auth = str(base64.b64encode(str(":" + app.config['DEFAULT']['auth']).encode('utf-8')), 'utf-8')
|
||||
cmd_auth = str(base64.b64encode(str(":" + app.config['DEFAULT']['cmd_auth']).encode('utf-8')), 'utf-8')
|
||||
hosts = app.config['DEFAULT']['hosts']
|
||||
port = app.config['DEFAULT']['port']
|
||||
cmd_port = app.config['DEFAULT']['cmd_port']
|
||||
hosts = app.config['DEFAULT']['hosts']
|
||||
port = app.config['DEFAULT']['port']
|
||||
cmd_port = app.config['DEFAULT']['cmd_port']
|
||||
useSSL = app.config['DEFAULT']['useSSL']
|
||||
CAfile = app.config['DEFAULT']['CAfile']
|
||||
sync_facility = app.config['DEFAULT']['sync_facility']
|
||||
|
||||
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
|
||||
# https://www.metageek.com/training/resources/understanding-rssi/
|
||||
|
||||
def isup(host_l, port):
|
||||
global DEBUG
|
||||
import socket
|
||||
# Rewrite using HTTPConnection
|
||||
def isup(listed_host, port):
|
||||
'''
|
||||
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)
|
||||
if useSSL:
|
||||
sslcontext = ssl.create_default_context()
|
||||
# ~ if os.path.exists(CAfile):
|
||||
# ~ sslcontext.load_verify_locations(cafile=CAfile)
|
||||
# ~ else:
|
||||
# Dont validate cert, we juts want to see if host is live
|
||||
# ~ sslcontext = ssl.create_default_context()
|
||||
# Disable cert validation, we just want to know if host is live
|
||||
sslcontext.check_hostname = False
|
||||
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:
|
||||
s.settimeout(3.0)
|
||||
s.connect((host_l, port))
|
||||
if DEBUG:
|
||||
print( _("Port {} reachable").format(str(port)) )
|
||||
s.connect((listed_host, port))
|
||||
if debug:
|
||||
print(_("Port {} reachable on {}").format(str(port), str(listed_host)))
|
||||
return 1
|
||||
except (socket.error, socket.timeout) as e:
|
||||
if DEBUG:
|
||||
print( _("Error on connection to {} : {} : {} ").format(host_l, str(port), e) )
|
||||
if debug:
|
||||
print(_("Error on connection to {} : {} : {} ").format(listed_host, str(port), e))
|
||||
return 0
|
||||
finally:
|
||||
s.close()
|
||||
|
||||
def checkHosts(host_l):
|
||||
hostdown, hostup = [], []
|
||||
hosts_number = str(len(host_l))
|
||||
for lhost in host_l:
|
||||
if not isup(lhost, port):
|
||||
hostdown.append(lhost)
|
||||
def check_hosts(host_list):
|
||||
'''
|
||||
Check hosts in a host list are up and build then return two lists with up/down hosts.
|
||||
'''
|
||||
hosts_up, hosts_down = [], []
|
||||
hosts_number = str(len(host_list))
|
||||
for local_host in host_list:
|
||||
if not isup(local_host, port):
|
||||
hosts_down.append(local_host)
|
||||
else:
|
||||
hostup.append(lhost)
|
||||
if DEBUG:
|
||||
print( _("{} of {} hosts found.").format(str(len(hostup)), hosts_number))
|
||||
return hostup, hostdown
|
||||
hosts_up.append(local_host)
|
||||
if debug:
|
||||
print( _("{} of {} hosts found.").format(str(len(hosts_up)), hosts_number))
|
||||
return hosts_up, hosts_down
|
||||
|
||||
# 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):
|
||||
files = os.listdir(folder);
|
||||
medias = []
|
||||
|
@ -144,23 +186,28 @@ def listMediaFiles(folder):
|
|||
else:
|
||||
return []
|
||||
|
||||
def httpUpload(filename, hostl, trailing_slash=1):
|
||||
import requests
|
||||
url = "https://" + hostl + ":" + str(cmd_port) + "/upload"
|
||||
def HTTP_upload(filename, host_local, port, trailing_slash=1):
|
||||
'''
|
||||
Build HTTP file upload request and send it.
|
||||
'''
|
||||
url = "https://" + host_local + ":" + str(port) + "/upload"
|
||||
if not trailing_slash:
|
||||
filename = "/" + filename
|
||||
files = { "file":( filename, open( media_folder_local + filename, "rb"), "multipart/form-data") }
|
||||
if DEBUG:
|
||||
if debug:
|
||||
print(files)
|
||||
resp = requests.post(url, files=files, headers=headers, verify=CAfile)
|
||||
if DEBUG:
|
||||
if debug:
|
||||
print(resp.text)
|
||||
if resp.ok:
|
||||
return 1
|
||||
else:
|
||||
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
|
||||
# Check for trailing / and add it if missing
|
||||
if media_folder_local[-1:] != "/":
|
||||
|
@ -168,30 +215,29 @@ def syncMediaFolder(media_folder_local, media_folder_remote, hostl, sync_facilit
|
|||
trailing_slash = 0
|
||||
if media_folder_remote[-1:] != "/":
|
||||
media_folder_remote += "/"
|
||||
#Using http_upload
|
||||
if sync_facility == "http":
|
||||
media_list = listMediaFiles(media_folder_local)
|
||||
media_list = list_media_files(media_folder_local)
|
||||
transfer_ok = 0
|
||||
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))
|
||||
|
||||
# Check sync utility exists
|
||||
elif which(sync_facility):
|
||||
from shutil import whichdb
|
||||
# Build subprocess arg list accroding to facility
|
||||
# Build subprocess arg list accroding to sync_facility
|
||||
# Using Rsync
|
||||
if sync_facility == "rsync":
|
||||
scrape_str = "total size is "
|
||||
sync_args = [sync_facility, "-zharm", "--include='*/'"]
|
||||
for media_type in media_exts:
|
||||
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":
|
||||
media_list = listMediaFiles(media_folder_local)
|
||||
media_list = list_media_files(media_folder_local)
|
||||
sync_args = [sync_facility, "-Crp", "-o IdentitiesOnly=yes"]
|
||||
for media in media_list:
|
||||
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)
|
||||
if len(sync_proc.stdout):
|
||||
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
|
||||
|
||||
# /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"
|
||||
fd = open(filename, "w")
|
||||
fd.write(m3u_content)
|
||||
|
@ -210,15 +259,24 @@ def writeM3U(m3u_content : str, host : str):
|
|||
|
||||
# String utilities
|
||||
|
||||
def escapeStr(uri):
|
||||
def escape_str(uri):
|
||||
'''
|
||||
Replace spaces with %20 for http urls
|
||||
'''
|
||||
return uri.replace(" ", "%20")
|
||||
|
||||
def sec2min(duration):
|
||||
'''
|
||||
Convert seconds to min:sec format.
|
||||
'''
|
||||
return('%02d:%02d' % (duration / 60, duration % 60))
|
||||
|
||||
# VLC lua utilities
|
||||
|
||||
def sendCommand(host, arg0, arg1, arg2):
|
||||
# TODO : make that a class
|
||||
def send_command(host, arg0, arg1, arg2):
|
||||
'''
|
||||
Build a http request according to args, send it and return parsed result.
|
||||
'''
|
||||
portl = port
|
||||
# Build request
|
||||
req = "/requests/status.xml"
|
||||
|
@ -235,19 +293,19 @@ def sendCommand(host, arg0, arg1, arg2):
|
|||
if (arg0 == "sort") or (arg0 == "move") :
|
||||
# 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
|
||||
req = req + "&val=" + escapeStr(arg2)
|
||||
req = req + "&val=" + escape_str(arg2)
|
||||
elif arg0 == "seek" :
|
||||
req = req + "&val=" + arg1
|
||||
elif (arg0 == "enqueue") or (arg0 == "add") :
|
||||
req = req + "&input=file://" + media_folder_remote + "/" + arg1
|
||||
# Send request
|
||||
if useSSL:
|
||||
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
|
||||
# ~ 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
|
||||
conn = http.client.HTTPSConnection( host + ":" + str(portl), timeout=3, context = sslcontext )
|
||||
else:
|
||||
conn = http.client.HTTPConnection( host + ":" + str(portl), timeout=3 )
|
||||
|
@ -266,7 +324,7 @@ def sendCommand(host, arg0, arg1, arg2):
|
|||
# ~ data = resp.read()
|
||||
|
||||
if arg0 == "rssi":
|
||||
if DEBUG:
|
||||
if debug:
|
||||
print(data)
|
||||
response_dict = {
|
||||
'host': host,
|
||||
|
@ -339,9 +397,7 @@ status_message = _("Idle")
|
|||
|
||||
@app.route("/")
|
||||
def main():
|
||||
global hosts
|
||||
status_message = _("Searching network for live hosts...")
|
||||
# ~ hosts_available, hosts_unavailable = checkHosts(hosts)
|
||||
templateData = {
|
||||
'hosts' : hosts,
|
||||
'status_message' : status_message,
|
||||
|
@ -352,13 +408,13 @@ def main():
|
|||
@app.route("/scan")
|
||||
def scan():
|
||||
global hosts_available, hosts_unavailable
|
||||
hosts_available, hosts_unavailable = checkHosts(hosts)
|
||||
hosts_available, hosts_unavailable = check_hosts(hosts)
|
||||
hosts_status = [hosts_available, hosts_unavailable]
|
||||
return hosts_status
|
||||
|
||||
@app.route("/browse")
|
||||
def browse():
|
||||
files = listMediaFiles(media_folder_local)
|
||||
files = list_media_files(media_folder_local)
|
||||
return files;
|
||||
|
||||
|
||||
|
@ -366,9 +422,9 @@ def browse():
|
|||
def sync(host):
|
||||
if host == "all":
|
||||
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:
|
||||
size = syncMediaFolder(media_folder_local, media_folder_remote, host)
|
||||
size = sync_media_folder(media_folder_local, media_folder_remote, host, cmd_port)
|
||||
return size;
|
||||
|
||||
|
||||
|
@ -382,13 +438,13 @@ def action(host, arg0, arg1, arg2):
|
|||
elif host == "all":
|
||||
resp = []
|
||||
for hostl in hosts_available:
|
||||
resp.append( sendCommand(hostl, arg0, arg1, arg2) )
|
||||
resp.append( send_command(hostl, arg0, arg1, arg2) )
|
||||
status_message = resp
|
||||
elif host not in hosts_available:
|
||||
status_message = "<p>{}</p>".format("Host is not reachable")
|
||||
else:
|
||||
status_message = sendCommand(host, arg0, arg1, arg2)
|
||||
if DEBUG:
|
||||
status_message = send_command(host, arg0, arg1, arg2)
|
||||
if debug:
|
||||
print(status_message)
|
||||
return status_message
|
||||
|
||||
|
|
|
@ -42,9 +42,7 @@ sha256 : ec3e17fc9b41f8c5181484e9866be2d1d92cab8403210e3d22f4f689edd4cfde
|
|||
md5 :
|
||||
sha256 :
|
||||
|
||||
* Add rt8821cu driver back
|
||||
* Use safe overclocking settings for rpi1, 3, 4, better memory split
|
||||
* Add blink function to pilpil
|
||||
|
||||
|
||||
# FS checklist
|
||||
* /etc/dhcpcd.conf
|
||||
|
@ -59,20 +57,28 @@ sha256 :
|
|||
|
||||
|
||||
# DOING NEXT :
|
||||
* ~ Test with several rpis
|
||||
* Define http auth secret at setup
|
||||
|
||||
|
||||
# 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:
|
||||
* get_client_rssi.sh on server
|
||||
|
||||
# TODO :
|
||||
* FR localisation
|
||||
* GUI : Btn hover/press ?
|
||||
|
||||
* ? python sanitization
|
||||
* ~ Test with several rpis
|
||||
* ? Scripts hotspot linux/win/mac
|
||||
* ? Config sync
|
||||
* ! Remove git personal details/resolv.conf, remove authorized_keys, ssh config, clean home, re-enable ssh pw login
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
[DEFAULT]
|
||||
DEBUG = 0
|
||||
debug = 0
|
||||
useSSL = false
|
||||
CAfile = "selfCA.crt"
|
||||
# scp, rsync, http
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
[DEFAULT]
|
||||
DEBUG = 0
|
||||
debug = 0
|
||||
useSSL = true
|
||||
CAfile = "selfCA.crt"
|
||||
# scp, rsync, http
|
||||
|
|
Loading…
Reference in New Issue