Formatting
This commit is contained in:
@ -1,4 +1,6 @@
|
||||
import logging, json, pathlib
|
||||
import logging
|
||||
import json
|
||||
import pathlib
|
||||
|
||||
import pkg_resources
|
||||
from mopidy import config, ext
|
||||
@ -37,7 +39,9 @@ class Extension(ext.Extension):
|
||||
from .frontend import IrisFrontend
|
||||
|
||||
# Add web extension
|
||||
registry.add("http:app", {"name": self.ext_name, "factory": iris_factory})
|
||||
registry.add(
|
||||
"http:app", {
|
||||
"name": self.ext_name, "factory": iris_factory})
|
||||
|
||||
# Add our frontend
|
||||
registry.add("frontend", IrisFrontend)
|
||||
|
||||
@ -1,13 +1,18 @@
|
||||
import random, string, logging, json, pathlib, pykka, urllib, os, sys, mopidy_iris, subprocess
|
||||
import random
|
||||
import string
|
||||
import logging
|
||||
import json
|
||||
import pathlib
|
||||
import pykka
|
||||
import urllib
|
||||
import os
|
||||
import sys
|
||||
import tornado.web
|
||||
import tornado.ioloop
|
||||
import requests
|
||||
import time
|
||||
import pickle
|
||||
from mopidy import config, ext
|
||||
from mopidy.core import CoreListener
|
||||
from pkg_resources import parse_version
|
||||
from tornado.escape import json_encode, json_decode
|
||||
from tornado.escape import json_encode
|
||||
from tornado.httpclient import AsyncHTTPClient, HTTPRequest
|
||||
from pathlib import Path
|
||||
|
||||
@ -75,7 +80,7 @@ class IrisCore(pykka.ThreadingActor):
|
||||
content = pickle.load(f)
|
||||
f.close()
|
||||
return content
|
||||
except Exception as e:
|
||||
except Exception:
|
||||
return {}
|
||||
|
||||
##
|
||||
@ -114,7 +119,8 @@ class IrisCore(pykka.ThreadingActor):
|
||||
# @return string
|
||||
##
|
||||
def generateGuid(self):
|
||||
return "".join(random.choices(string.ascii_uppercase + string.digits, k=12))
|
||||
return "".join(random.choices(
|
||||
string.ascii_uppercase + string.digits, k=12))
|
||||
|
||||
##
|
||||
# Digest a protocol header into it's id/name parts
|
||||
@ -124,10 +130,12 @@ class IrisCore(pykka.ThreadingActor):
|
||||
def digest_protocol(self, protocol):
|
||||
|
||||
# if we're a string, split into list
|
||||
# this handles the different ways we get this passed (select_subprotocols gives string, headers.get gives list)
|
||||
if isinstance(protocol, basestring):
|
||||
# this handles the different ways we get this passed
|
||||
# (select_subprotocols gives string, headers.get gives list)
|
||||
if isinstance(protocol, str):
|
||||
|
||||
# make sure we strip any spaces (IE gives "element,element", proper browsers give "element, element")
|
||||
# make sure we strip any spaces (IE gives "element,element", proper
|
||||
# browsers give "element, element")
|
||||
protocol = [i.strip() for i in protocol.split(",")]
|
||||
|
||||
# if we've been given a valid array
|
||||
@ -138,7 +146,7 @@ class IrisCore(pykka.ThreadingActor):
|
||||
generated = False
|
||||
|
||||
# invalid, so just create a default connection, and auto-generate an ID
|
||||
except:
|
||||
except BaseException:
|
||||
client_id = self.generateGuid()
|
||||
connection_id = self.generateGuid()
|
||||
username = "Anonymous"
|
||||
@ -197,7 +205,7 @@ class IrisCore(pykka.ThreadingActor):
|
||||
callback(response)
|
||||
else:
|
||||
return response
|
||||
except:
|
||||
except BaseException:
|
||||
error = "Failed to send message to " + data["recipient"]
|
||||
logger.error(error)
|
||||
|
||||
@ -234,9 +242,8 @@ class IrisCore(pykka.ThreadingActor):
|
||||
if send_to_this_connection:
|
||||
connection["connection"].write_message(json_encode(message))
|
||||
|
||||
response = {
|
||||
"message": "Broadcast to " + str(len(self.connections)) + " connections"
|
||||
}
|
||||
response = {"message": "Broadcast to " +
|
||||
str(len(self.connections)) + " connections"}
|
||||
if callback:
|
||||
callback(response)
|
||||
else:
|
||||
@ -245,9 +252,9 @@ class IrisCore(pykka.ThreadingActor):
|
||||
##
|
||||
# Connections
|
||||
#
|
||||
# Contains all our connections and client details. This requires updates
|
||||
# when new clients connect, and old ones disconnect. These events are broadcast
|
||||
# to all current connections
|
||||
# Contains all our connections and client details. This requires
|
||||
# updates when new clients connect, and old ones disconnect. These
|
||||
# events are broadcast to all current connections
|
||||
##
|
||||
|
||||
def get_connections(self, *args, **kwargs):
|
||||
@ -277,7 +284,10 @@ class IrisCore(pykka.ThreadingActor):
|
||||
}
|
||||
|
||||
self.broadcast(
|
||||
data={"method": "connection_added", "params": {"connection": client}}
|
||||
data={
|
||||
"method": "connection_added",
|
||||
"params": {
|
||||
"connection": client}}
|
||||
)
|
||||
|
||||
def update_connection(self, *args, **kwargs):
|
||||
@ -286,15 +296,19 @@ class IrisCore(pykka.ThreadingActor):
|
||||
connection_id = data["connection_id"]
|
||||
|
||||
if connection_id in self.connections:
|
||||
self.connections[connection_id]["client"]["username"] = data["username"]
|
||||
self.connections[connection_id]["client"]["client_id"] = data["client_id"]
|
||||
username = data["username"]
|
||||
client_id = data["client_id"]
|
||||
self.connections[connection_id]["client"]["username"] = username
|
||||
self.connections[connection_id]["client"]["client_id"] = client_id
|
||||
self.broadcast(
|
||||
data={
|
||||
"method": "connection_changed",
|
||||
"params": {"connection": self.connections[connection_id]["client"]},
|
||||
}
|
||||
)
|
||||
response = {"connection": self.connections[connection_id]["client"]}
|
||||
"params": {
|
||||
"connection":
|
||||
self.connections[connection_id]["client"]},
|
||||
})
|
||||
response = {
|
||||
"connection": self.connections[connection_id]["client"]}
|
||||
if callback:
|
||||
callback(response)
|
||||
else:
|
||||
@ -321,7 +335,7 @@ class IrisCore(pykka.ThreadingActor):
|
||||
"params": {"connection": client},
|
||||
}
|
||||
)
|
||||
except:
|
||||
except BaseException:
|
||||
logger.error("Failed to close connection to " + connection_id)
|
||||
|
||||
def set_username(self, *args, **kwargs):
|
||||
@ -330,14 +344,18 @@ class IrisCore(pykka.ThreadingActor):
|
||||
connection_id = data["connection_id"]
|
||||
|
||||
if connection_id in self.connections:
|
||||
self.connections[connection_id]["client"]["username"] = data["username"]
|
||||
username = data["username"]
|
||||
self.connections[connection_id]["client"]["username"] = username
|
||||
self.broadcast(
|
||||
data={
|
||||
"method": "connection_changed",
|
||||
"params": {"connection": self.connections[connection_id]["client"]},
|
||||
}
|
||||
)
|
||||
response = {"connection_id": connection_id, "username": data["username"]}
|
||||
"params": {
|
||||
"connection":
|
||||
self.connections[connection_id]["client"]},
|
||||
})
|
||||
response = {
|
||||
"connection_id": connection_id,
|
||||
"username": data["username"]}
|
||||
if callback:
|
||||
callback(response)
|
||||
else:
|
||||
@ -363,7 +381,8 @@ class IrisCore(pykka.ThreadingActor):
|
||||
callback = kwargs.get("callback", False)
|
||||
|
||||
# handle config setups where there is no username/password
|
||||
# Iris won't work properly anyway, but at least we won't get server errors
|
||||
# Iris won't work properly anyway, but at least we won't get server
|
||||
# errors
|
||||
if "spotify" in self.config and "username" in self.config["spotify"]:
|
||||
spotify_username = self.config["spotify"]["username"]
|
||||
else:
|
||||
@ -410,7 +429,7 @@ class IrisCore(pykka.ThreadingActor):
|
||||
)
|
||||
upgrade_available = upgrade_available == 1
|
||||
|
||||
except (urllib.request.HTTPError, urllib.request.URLError) as e:
|
||||
except (urllib.request.HTTPError, urllib.request.URLError):
|
||||
latest_version = "0.0.0"
|
||||
upgrade_available = False
|
||||
|
||||
@ -450,9 +469,15 @@ class IrisCore(pykka.ThreadingActor):
|
||||
if error:
|
||||
self.broadcast(data={"method": "restart_error", "params": error})
|
||||
elif update:
|
||||
self.broadcast(data={"method": "restart_updated", "params": update})
|
||||
self.broadcast(
|
||||
data={
|
||||
"method": "restart_updated",
|
||||
"params": update})
|
||||
else:
|
||||
self.broadcast(data={"method": "restart_finished", "params": response})
|
||||
self.broadcast(
|
||||
data={
|
||||
"method": "restart_finished",
|
||||
"params": response})
|
||||
|
||||
##
|
||||
# Run an upgrade of Iris
|
||||
@ -477,9 +502,15 @@ class IrisCore(pykka.ThreadingActor):
|
||||
if error:
|
||||
self.broadcast(data={"method": "upgrade_error", "params": error})
|
||||
elif update:
|
||||
self.broadcast(data={"method": "upgrade_updated", "params": update})
|
||||
self.broadcast(
|
||||
data={
|
||||
"method": "upgrade_updated",
|
||||
"params": update})
|
||||
else:
|
||||
self.broadcast(data={"method": "upgrade_finished", "params": response})
|
||||
self.broadcast(
|
||||
data={
|
||||
"method": "upgrade_finished",
|
||||
"params": response})
|
||||
self.restart()
|
||||
|
||||
##
|
||||
@ -491,7 +522,10 @@ class IrisCore(pykka.ThreadingActor):
|
||||
ioloop = kwargs.get("ioloop", False)
|
||||
|
||||
# Trigger the action
|
||||
IrisSystemThread("local_scan", ioloop, self.local_scan_callback).start()
|
||||
IrisSystemThread(
|
||||
"local_scan",
|
||||
ioloop,
|
||||
self.local_scan_callback).start()
|
||||
|
||||
self.broadcast(data={"method": "local_scan_started"})
|
||||
|
||||
@ -503,18 +537,28 @@ class IrisCore(pykka.ThreadingActor):
|
||||
|
||||
def local_scan_callback(self, response, error, update):
|
||||
if error:
|
||||
self.broadcast(data={"method": "local_scan_error", "params": error})
|
||||
self.broadcast(
|
||||
data={
|
||||
"method": "local_scan_error",
|
||||
"params": error})
|
||||
elif update:
|
||||
self.broadcast(data={"method": "local_scan_updated", "params": update})
|
||||
self.broadcast(
|
||||
data={
|
||||
"method": "local_scan_updated",
|
||||
"params": update})
|
||||
else:
|
||||
self.broadcast(data={"method": "local_scan_finished", "params": response})
|
||||
self.broadcast(
|
||||
data={
|
||||
"method": "local_scan_finished",
|
||||
"params": response})
|
||||
|
||||
##
|
||||
# Spotify Radio
|
||||
#
|
||||
# Accepts seed URIs and creates radio-like experience. When our tracklist is nearly
|
||||
# empty, we fetch more recommendations. This can result in duplicates. We keep the
|
||||
# recommendations limit low to avoid timeouts and slow UI
|
||||
# Accepts seed URIs and creates radio-like experience. When our
|
||||
# tracklist is nearly empty, we fetch more recommendations. This
|
||||
# can result in duplicates. We keep the recommendations limit low
|
||||
# to avoid timeouts and slow UI
|
||||
##
|
||||
|
||||
def get_radio(self, *args, **kwargs):
|
||||
@ -578,11 +622,17 @@ class IrisCore(pykka.ThreadingActor):
|
||||
if starting:
|
||||
self.core.playback.play()
|
||||
self.broadcast(
|
||||
data={"method": "radio_started", "params": {"radio": self.radio}}
|
||||
data={
|
||||
"method": "radio_started",
|
||||
"params": {
|
||||
"radio": self.radio}}
|
||||
)
|
||||
else:
|
||||
self.broadcast(
|
||||
data={"method": "radio_changed", "params": {"radio": self.radio}}
|
||||
data={
|
||||
"method": "radio_changed",
|
||||
"params": {
|
||||
"radio": self.radio}}
|
||||
)
|
||||
|
||||
self.get_radio(callback=callback)
|
||||
@ -632,7 +682,7 @@ class IrisCore(pykka.ThreadingActor):
|
||||
await self.get_spotify_token()
|
||||
spotify_token = self.spotify_token
|
||||
access_token = spotify_token["access_token"]
|
||||
except:
|
||||
except BaseException:
|
||||
error = "IrisFrontend: access_token missing or invalid"
|
||||
logger.error(error)
|
||||
return False
|
||||
@ -641,24 +691,29 @@ class IrisCore(pykka.ThreadingActor):
|
||||
url = (
|
||||
url
|
||||
+ "?seed_artists="
|
||||
+ (",".join(self.radio["seed_artists"])).replace("spotify:artist:", "")
|
||||
+ (",".join(self.radio["seed_artists"])
|
||||
).replace("spotify:artist:", "")
|
||||
)
|
||||
url = (
|
||||
url
|
||||
+ "&seed_genres="
|
||||
+ (",".join(self.radio["seed_genres"])).replace("spotify:genre:", "")
|
||||
+ (",".join(self.radio["seed_genres"])
|
||||
).replace("spotify:genre:", "")
|
||||
)
|
||||
url = (
|
||||
url
|
||||
+ "&seed_tracks="
|
||||
+ (",".join(self.radio["seed_tracks"])).replace("spotify:track:", "")
|
||||
+ (",".join(self.radio["seed_tracks"])
|
||||
).replace("spotify:track:", "")
|
||||
)
|
||||
url = url + "&limit=50"
|
||||
http_client = AsyncHTTPClient()
|
||||
|
||||
try:
|
||||
http_response = await http_client.fetch(
|
||||
url, "POST", headers={"Authorization": "Bearer " + access_token}
|
||||
url, "POST", headers={
|
||||
"Authorization": "Bearer " + access_token
|
||||
}
|
||||
)
|
||||
response_body = json.loads(http_response.body)
|
||||
|
||||
@ -675,7 +730,8 @@ class IrisCore(pykka.ThreadingActor):
|
||||
+ error["error_description"]
|
||||
}
|
||||
logger.error(
|
||||
"Could not fetch Spotify recommendations: " + error["error_description"]
|
||||
"Could not fetch Spotify recommendations: " +
|
||||
error["error_description"]
|
||||
)
|
||||
logger.debug(error)
|
||||
return False
|
||||
@ -687,7 +743,8 @@ class IrisCore(pykka.ThreadingActor):
|
||||
# Grab our loaded tracks
|
||||
uris = self.radio["results"]
|
||||
|
||||
# We've run out of pre-fetched tracks, so we need to get more recommendations
|
||||
# We've run out of pre-fetched tracks, so we need to get more
|
||||
# recommendations
|
||||
if len(uris) < 3:
|
||||
uris = self.load_more_tracks()
|
||||
|
||||
@ -753,8 +810,10 @@ class IrisCore(pykka.ThreadingActor):
|
||||
for tlid in data["tlids"]:
|
||||
item = {
|
||||
"tlid": tlid,
|
||||
"added_from": data["added_from"] if "added_from" in data else None,
|
||||
"added_by": data["added_by"] if "added_by" in data else None,
|
||||
"added_from":
|
||||
data["added_from"] if "added_from" in data else None,
|
||||
"added_by":
|
||||
data["added_by"] if "added_by" in data else None,
|
||||
}
|
||||
self.queue_metadata["tlid_" + str(tlid)] = item
|
||||
|
||||
@ -772,12 +831,12 @@ class IrisCore(pykka.ThreadingActor):
|
||||
return response
|
||||
|
||||
def clean_queue_metadata(self, *args, **kwargs):
|
||||
callback = kwargs.get("callback", False)
|
||||
cleaned_queue_metadata = {}
|
||||
|
||||
for tltrack in self.core.tracklist.get_tl_tracks().get():
|
||||
|
||||
# if we have metadata for this track, push it through to cleaned dictionary
|
||||
# if we have metadata for this track, push it through to cleaned
|
||||
# dictionary
|
||||
if "tlid_" + str(tltrack.tlid) in self.queue_metadata:
|
||||
cleaned_queue_metadata[
|
||||
"tlid_" + str(tltrack.tlid)
|
||||
@ -811,7 +870,10 @@ class IrisCore(pykka.ThreadingActor):
|
||||
self.save_to_file(self.commands, "commands")
|
||||
|
||||
self.broadcast(
|
||||
data={"method": "commands_changed", "params": {"commands": self.commands}}
|
||||
data={
|
||||
"method": "commands_changed",
|
||||
"params": {
|
||||
"commands": self.commands}}
|
||||
)
|
||||
|
||||
response = {"message": "Commands saved"}
|
||||
@ -822,14 +884,14 @@ class IrisCore(pykka.ThreadingActor):
|
||||
|
||||
async def run_command(self, *args, **kwargs):
|
||||
callback = kwargs.get("callback", False)
|
||||
ioloop = kwargs.get("ioloop", False)
|
||||
data = kwargs.get("data", {})
|
||||
error = False
|
||||
|
||||
if str(data["id"]) not in self.commands:
|
||||
error = {
|
||||
"message": "Command failed",
|
||||
"description": 'Could not find command by ID "' + str(data["id"]) + '"',
|
||||
"description": r'''Could not find command by ID
|
||||
"' + str(data["id"]) + '"''',
|
||||
}
|
||||
else:
|
||||
command = self.commands[str(data["id"])]
|
||||
@ -859,7 +921,8 @@ class IrisCore(pykka.ThreadingActor):
|
||||
d = command["additional_headers"].split("\n")
|
||||
lines = list(filter(lambda x: x.find(":") > 0, d))
|
||||
fields = [
|
||||
(x.split(":", 1)[0].strip().lower(), x.split(":", 1)[1].strip())
|
||||
(x.split(":", 1)[0].strip().lower(),
|
||||
x.split(":", 1)[1].strip())
|
||||
for x in lines
|
||||
]
|
||||
headers = dict(fields)
|
||||
@ -882,8 +945,10 @@ class IrisCore(pykka.ThreadingActor):
|
||||
)
|
||||
else:
|
||||
request = HTTPRequest(
|
||||
command["url"], connect_timeout=5, validate_cert=False, headers=headers
|
||||
)
|
||||
command["url"],
|
||||
connect_timeout=5,
|
||||
validate_cert=False,
|
||||
headers=headers)
|
||||
|
||||
# Make the request, and handle any request errors
|
||||
try:
|
||||
@ -900,15 +965,18 @@ class IrisCore(pykka.ThreadingActor):
|
||||
# Attempt to parse body as JSON
|
||||
try:
|
||||
command_response_body = json.loads(command_response.body)
|
||||
except:
|
||||
except BaseException:
|
||||
# Perhaps it requires unicode encoding?
|
||||
try:
|
||||
command_response_body = tornado.escape.to_unicode(command_response.body)
|
||||
except:
|
||||
command_response_body = tornado.escape.to_unicode(
|
||||
command_response.body)
|
||||
except BaseException:
|
||||
command_response_body = ""
|
||||
|
||||
# Finally, return the result
|
||||
response = {"message": "Command run", "response": command_response_body}
|
||||
response = {
|
||||
"message": "Command run",
|
||||
"response": command_response_body}
|
||||
|
||||
if callback:
|
||||
callback(response)
|
||||
@ -919,16 +987,20 @@ class IrisCore(pykka.ThreadingActor):
|
||||
##
|
||||
# Spotify authentication
|
||||
#
|
||||
# Uses the Client Credentials Flow, so is invisible to the user. We need this token for
|
||||
# any backend spotify requests (we don't tap in to Mopidy-Spotify, yet). Also used for
|
||||
# passing token to frontend for javascript requests without use of the Authorization Code Flow.
|
||||
# Uses the Client Credentials Flow, so is invisible to the user.
|
||||
# We need this token for any backend spotify requests (we don't tap in
|
||||
# to Mopidy-Spotify, yet). Also used for passing token to frontend for
|
||||
# javascript requests without use of the Authorization Code Flow.
|
||||
##
|
||||
|
||||
async def get_spotify_token(self, *args, **kwargs):
|
||||
callback = kwargs.get("callback", False)
|
||||
|
||||
# Expired, so go get a new one
|
||||
if not self.spotify_token or self.spotify_token["expires_at"] <= time.time():
|
||||
if (
|
||||
not self.spotify_token or
|
||||
self.spotify_token["expires_at"] <= time.time()
|
||||
):
|
||||
await self.refresh_spotify_token()
|
||||
|
||||
response = {"spotify_token": self.spotify_token}
|
||||
@ -977,8 +1049,8 @@ class IrisCore(pykka.ThreadingActor):
|
||||
except (urllib.error.HTTPError, urllib.error.URLError) as e:
|
||||
error = json.loads(e.read())
|
||||
error = {
|
||||
"message": "Could not refresh token: " + error["error_description"]
|
||||
}
|
||||
"message": "Could not refresh token: " +
|
||||
error["error_description"]}
|
||||
|
||||
if callback:
|
||||
callback(False, error)
|
||||
@ -997,9 +1069,10 @@ class IrisCore(pykka.ThreadingActor):
|
||||
##
|
||||
# Spotify authentication
|
||||
#
|
||||
# Uses the Client Credentials Flow, so is invisible to the user. We need this token for
|
||||
# any backend spotify requests (we don't tap in to Mopidy-Spotify, yet). Also used for
|
||||
# passing token to frontend for javascript requests without use of the Authorization Code Flow.
|
||||
# Uses the Client Credentials Flow, so is invisible to the user.
|
||||
# We need this token for any backend spotify requests (we don't tap in
|
||||
# to Mopidy-Spotify, yet). Also used for passing token to frontend for
|
||||
# javascript requests without use of the Authorization Code Flow.
|
||||
##
|
||||
|
||||
async def get_lyrics(self, *args, **kwargs):
|
||||
@ -1021,7 +1094,9 @@ class IrisCore(pykka.ThreadingActor):
|
||||
if connection_id not in self.connections:
|
||||
error = {
|
||||
"message": "Unauthorized request",
|
||||
"description": "Connection " + connection_id + " not connected",
|
||||
"description": "Connection " +
|
||||
connection_id +
|
||||
" not connected",
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
@ -1037,7 +1112,11 @@ class IrisCore(pykka.ThreadingActor):
|
||||
try:
|
||||
http_client = AsyncHTTPClient()
|
||||
http_response = await http_client.fetch(url)
|
||||
callback(http_response.body.decode("utf-8", errors="replace"), False)
|
||||
callback(
|
||||
http_response.body.decode(
|
||||
"utf-8",
|
||||
errors="replace"),
|
||||
False)
|
||||
|
||||
except (urllib.error.HTTPError, urllib.error.URLError) as e:
|
||||
error = json.loads(e.read())
|
||||
@ -1046,7 +1125,8 @@ class IrisCore(pykka.ThreadingActor):
|
||||
+ error["error_description"]
|
||||
}
|
||||
logger.error(
|
||||
"Could not fetch Spotify recommendations: " + error["error_description"]
|
||||
"Could not fetch Spotify recommendations: " +
|
||||
error["error_description"]
|
||||
)
|
||||
logger.debug(error)
|
||||
return error
|
||||
@ -1075,4 +1155,7 @@ class IrisCore(pykka.ThreadingActor):
|
||||
elif error:
|
||||
self.broadcast(data={"method": "test_updated", "params": update})
|
||||
else:
|
||||
self.broadcast(data={"method": "test_finished", "params": response})
|
||||
self.broadcast(
|
||||
data={
|
||||
"method": "test_finished",
|
||||
"params": response})
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import pykka, logging, tornado, functools
|
||||
import pykka
|
||||
import logging
|
||||
import functools
|
||||
from mopidy.core import CoreListener
|
||||
from .core import IrisCore
|
||||
from .mem import iris
|
||||
|
||||
# import logger
|
||||
@ -22,7 +23,9 @@ class IrisFrontend(pykka.ThreadingActor, CoreListener):
|
||||
iris.stop()
|
||||
|
||||
def track_playback_ended(self, tl_track, time_position):
|
||||
iris.ioloop.add_callback(functools.partial(iris.check_for_radio_update))
|
||||
iris.ioloop.add_callback(
|
||||
functools.partial(
|
||||
iris.check_for_radio_update))
|
||||
|
||||
def tracklist_changed(self):
|
||||
iris.ioloop.add_callback(functools.partial(iris.clean_queue_metadata))
|
||||
|
||||
@ -1,7 +1,13 @@
|
||||
from datetime import datetime
|
||||
from tornado.escape import json_encode, json_decode
|
||||
import tornado.ioloop, tornado.web, tornado.websocket, tornado.template
|
||||
import random, string, logging, uuid, subprocess, pykka, ast, logging, json, urllib, requests, time, asyncio
|
||||
import tornado.ioloop
|
||||
import tornado.web
|
||||
import tornado.websocket
|
||||
import tornado.template
|
||||
import logging
|
||||
import json
|
||||
import time
|
||||
import asyncio
|
||||
|
||||
from .mem import iris
|
||||
|
||||
@ -24,10 +30,12 @@ class WebsocketHandler(tornado.websocket.WebSocketHandler):
|
||||
|
||||
# Get the client's IP. If it's local, then use it's proxy origin
|
||||
ip = self.request.remote_ip
|
||||
if ip == "127.0.0.1" and hasattr(self.request.headers, "X-Forwarded-For"):
|
||||
if ip == "127.0.0.1" and hasattr(
|
||||
self.request.headers, "X-Forwarded-For"):
|
||||
ip = self.request.headers["X-Forwarded-For"]
|
||||
|
||||
# Construct our initial client object, and add to our list of connections
|
||||
# Construct our initial client object, and add to our list of
|
||||
# connections
|
||||
client = {
|
||||
"connection_id": iris.generateGuid(),
|
||||
"ip": ip,
|
||||
@ -54,7 +62,8 @@ class WebsocketHandler(tornado.websocket.WebSocketHandler):
|
||||
error={
|
||||
"id": id,
|
||||
"code": 32602,
|
||||
"message": 'Invalid JSON-RPC request (missing property "jsonrpc")',
|
||||
"message": r'''Invalid JSON-RPC request (missing
|
||||
property "jsonrpc")''',
|
||||
},
|
||||
)
|
||||
|
||||
@ -75,12 +84,15 @@ class WebsocketHandler(tornado.websocket.WebSocketHandler):
|
||||
if hasattr(iris, message["method"]):
|
||||
try:
|
||||
|
||||
# For async methods we need to await, but it must be ommited for syncronous methods
|
||||
if asyncio.iscoroutinefunction(getattr(iris, message["method"])):
|
||||
# For async methods we need to await, but it must be
|
||||
# ommited for syncronous methods
|
||||
if asyncio.iscoroutinefunction(
|
||||
getattr(iris, message["method"])):
|
||||
await getattr(iris, message["method"])(
|
||||
ioloop=self.ioloop,
|
||||
data=params,
|
||||
callback=lambda response, error=False: self.handle_result(
|
||||
callback=lambda response,
|
||||
error=False: self.handle_result(
|
||||
id=id,
|
||||
method=message["method"],
|
||||
response=response,
|
||||
@ -88,10 +100,13 @@ class WebsocketHandler(tornado.websocket.WebSocketHandler):
|
||||
),
|
||||
)
|
||||
else:
|
||||
getattr(iris, message["method"])(
|
||||
getattr(
|
||||
iris,
|
||||
message["method"])(
|
||||
ioloop=self.ioloop,
|
||||
data=params,
|
||||
callback=lambda response, error=False: self.handle_result(
|
||||
callback=lambda response,
|
||||
error=False: self.handle_result(
|
||||
id=id,
|
||||
method=message["method"],
|
||||
response=response,
|
||||
@ -106,7 +121,9 @@ class WebsocketHandler(tornado.websocket.WebSocketHandler):
|
||||
error={
|
||||
"id": id,
|
||||
"code": 32601,
|
||||
"message": 'Method "' + message["method"] + '" does not exist',
|
||||
"message": 'Method "' +
|
||||
message["method"] +
|
||||
'" does not exist',
|
||||
},
|
||||
id=id,
|
||||
)
|
||||
@ -142,7 +159,8 @@ class WebsocketHandler(tornado.websocket.WebSocketHandler):
|
||||
request_response["error"] = error
|
||||
|
||||
# We've been handed an AsyncHTTPClient callback. This is the case
|
||||
# when our request calls subsequent external requests (eg Spotify, Genius)
|
||||
# when our request calls subsequent external requests (eg Spotify,
|
||||
# Genius)
|
||||
elif isinstance(response, tornado.httpclient.HTTPResponse):
|
||||
request_response["result"] = response.body
|
||||
|
||||
@ -161,7 +179,8 @@ class HttpHandler(tornado.web.RequestHandler):
|
||||
self.set_header("Access-Control-Allow-Origin", "*")
|
||||
self.set_header(
|
||||
"Access-Control-Allow-Headers",
|
||||
"Origin, X-Requested-With, Content-Type, Accept, Authorization, Client-Security-Token, Accept-Encoding",
|
||||
r'''Origin, X-Requested-With, Content-Type, Accept,
|
||||
Authorization, Client-Security-Token, Accept-Encoding''',
|
||||
)
|
||||
|
||||
def initialize(self, core, config):
|
||||
@ -183,21 +202,29 @@ class HttpHandler(tornado.web.RequestHandler):
|
||||
if hasattr(iris, slug):
|
||||
try:
|
||||
|
||||
# For async methods we need to await, but it must be ommited for syncronous methods
|
||||
# For async methods we need to await, but it must be ommited
|
||||
# for syncronous methods
|
||||
if asyncio.iscoroutinefunction(getattr(iris, slug)):
|
||||
await getattr(iris, slug)(
|
||||
ioloop=self.ioloop,
|
||||
request=self,
|
||||
callback=lambda response, error=False: self.handle_result(
|
||||
callback=lambda response,
|
||||
error=False: self.handle_result(
|
||||
id=id, method=slug, response=response, error=error
|
||||
),
|
||||
)
|
||||
else:
|
||||
getattr(iris, slug)(
|
||||
getattr(
|
||||
iris,
|
||||
slug)(
|
||||
ioloop=self.ioloop,
|
||||
request=self,
|
||||
callback=lambda response, error=False: self.handle_result(
|
||||
id=id, method=slug, response=response, error=error
|
||||
callback=lambda response,
|
||||
error=False: self.handle_result(
|
||||
id=id,
|
||||
method=slug,
|
||||
response=response,
|
||||
error=error
|
||||
),
|
||||
)
|
||||
except Exception as e:
|
||||
@ -206,7 +233,11 @@ class HttpHandler(tornado.web.RequestHandler):
|
||||
else:
|
||||
self.handle_result(
|
||||
id=id,
|
||||
error={"code": 32601, "message": "Method " + slug + " does not exist"},
|
||||
error={
|
||||
"code": 32601,
|
||||
"message": "Method " +
|
||||
slug +
|
||||
" does not exist"},
|
||||
)
|
||||
return
|
||||
|
||||
@ -216,9 +247,10 @@ class HttpHandler(tornado.web.RequestHandler):
|
||||
|
||||
try:
|
||||
params = json.loads(self.request.body.decode("utf-8"))
|
||||
except:
|
||||
except BaseException:
|
||||
self.handle_result(
|
||||
id=id, error={"code": 32700, "message": "Missing or invalid payload"}
|
||||
id=id, error={
|
||||
"code": 32700, "message": "Missing or invalid payload"}
|
||||
)
|
||||
return
|
||||
|
||||
@ -229,29 +261,41 @@ class HttpHandler(tornado.web.RequestHandler):
|
||||
await getattr(iris, slug)(
|
||||
data=params,
|
||||
request=self.request,
|
||||
callback=lambda response=False, error=False: self.handle_result(
|
||||
callback=lambda response=False,
|
||||
error=False: self.handle_result(
|
||||
id=id, method=slug, response=response, error=error
|
||||
),
|
||||
)
|
||||
else:
|
||||
getattr(iris, slug)(
|
||||
getattr(
|
||||
iris,
|
||||
slug)(
|
||||
data=params,
|
||||
request=self.request,
|
||||
callback=lambda response=False, error=False: self.handle_result(
|
||||
id=id, method=slug, response=response, error=error
|
||||
callback=lambda response=False,
|
||||
error=False: self.handle_result(
|
||||
id=id,
|
||||
method=slug,
|
||||
response=response,
|
||||
error=error
|
||||
),
|
||||
)
|
||||
|
||||
except tornado.web.HTTPError as e:
|
||||
except tornado.web.HTTPError:
|
||||
self.handle_result(
|
||||
id=id, error={"code": 32601, "message": "Invalid JSON payload"}
|
||||
id=id, error={
|
||||
"code": 32601, "message": "Invalid JSON payload"}
|
||||
)
|
||||
return
|
||||
|
||||
else:
|
||||
self.handle_result(
|
||||
id=id,
|
||||
error={"code": 32601, "message": "Method " + slug + " does not exist"},
|
||||
error={
|
||||
"code": 32601,
|
||||
"message": "Method " +
|
||||
slug +
|
||||
" does not exist"},
|
||||
)
|
||||
return
|
||||
|
||||
@ -271,15 +315,17 @@ class HttpHandler(tornado.web.RequestHandler):
|
||||
self.set_status(400)
|
||||
|
||||
# We've been handed an AsyncHTTPClient callback. This is the case
|
||||
# when our request calls subsequent external requests (eg Spotify, Genius).
|
||||
# when our request calls subsequent external requests.
|
||||
# We don't need to wrap non-HTTPResponse responses as these are dicts
|
||||
elif isinstance(response, tornado.httpclient.HTTPResponse):
|
||||
|
||||
# Digest JSON responses into JSON
|
||||
content_type = response.headers.get("Content-Type")
|
||||
if content_type.startswith("application/json") or content_type.startswith(
|
||||
"text/json"
|
||||
):
|
||||
if content_type.startswith(
|
||||
"application/json"
|
||||
) or content_type.startswith(
|
||||
"text/json"
|
||||
):
|
||||
body = json.loads(response.body)
|
||||
|
||||
# Non-JSON so just copy as-is
|
||||
|
||||
@ -1,5 +1,8 @@
|
||||
from threading import Thread
|
||||
import logging, os, pathlib, subprocess, json, sys
|
||||
import logging
|
||||
import os
|
||||
import pathlib
|
||||
import subprocess
|
||||
|
||||
# import logger
|
||||
logger = logging.getLogger(__name__)
|
||||
@ -14,9 +17,9 @@ class IrisSystemPermissionError(IrisSystemError):
|
||||
|
||||
def __init__(self, path):
|
||||
message = (
|
||||
"Password-less access to %s was refused. Check your /etc/sudoers file."
|
||||
% path.as_uri()
|
||||
)
|
||||
r'''Password-less access to %s was refused.
|
||||
Check your /etc/sudoers file.''' %
|
||||
path.as_uri())
|
||||
logger.error(message)
|
||||
super().__init__(message)
|
||||
|
||||
@ -63,7 +66,8 @@ class IrisSystemThread(Thread):
|
||||
|
||||
command = self.get_command()
|
||||
logger.debug("Running '%s'", os.fsdecode(b" ".join(command)))
|
||||
process = subprocess.Popen(command, stdout=subprocess.PIPE, encoding="utf8")
|
||||
process = subprocess.Popen(
|
||||
command, stdout=subprocess.PIPE, encoding="utf8")
|
||||
|
||||
lines = ""
|
||||
while True:
|
||||
@ -73,8 +77,6 @@ class IrisSystemThread(Thread):
|
||||
if line:
|
||||
logger.info(line)
|
||||
lines = lines + "\n" + line
|
||||
# This seems to be ignored. Detected as spammy io?
|
||||
# self.ioloop.add_callback(lambda: self.callback(None, None, {'output': line}))
|
||||
|
||||
if process.returncode == 0:
|
||||
self.ioloop.add_callback(
|
||||
@ -92,10 +94,14 @@ class IrisSystemThread(Thread):
|
||||
##
|
||||
def can_run(self, *args, **kwargs):
|
||||
# Attempt an empty call to our system file
|
||||
command_bytes = b" ".join(self.get_command("check", non_interactive=True))
|
||||
command_bytes = b" ".join(
|
||||
self.get_command(
|
||||
"check", non_interactive=True))
|
||||
process = subprocess.Popen(
|
||||
command_bytes, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True
|
||||
)
|
||||
command_bytes,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
shell=True)
|
||||
result, error = process.communicate()
|
||||
exitCode = process.wait()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user