rpc.py 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. import logging
  2. import gc
  3. from copy import deepcopy
  4. from greenlet import greenlet
  5. from flask import (
  6. current_app,
  7. Blueprint,
  8. Flask,
  9. jsonify,
  10. render_template,
  11. request,
  12. abort,
  13. redirect,
  14. )
  15. from flask_bootstrap import Bootstrap
  16. from flask.ext.babel import Babel
  17. from werkzeug.contrib.cache import SimpleCache
  18. from .dbadmin import db
  19. from .config import get_conf
  20. rpc = Blueprint("rpc", __name__, url_prefix=get_conf()["ROUTE_PREFIX"] + "/api")
  21. viewui = Blueprint(
  22. "view",
  23. __name__,
  24. url_prefix=get_conf()["ROUTE_PREFIX"] + "/view",
  25. template_folder="templates",
  26. )
  27. def send_to_parent(kind, *args):
  28. """similar to the behaviour of a ParentedLet"""
  29. if not hasattr(current_app, "queue"):
  30. logging.debug("no parent queue; aborting send")
  31. return
  32. msg = {
  33. "emitter": current_app._get_current_object(),
  34. "class": current_app._get_current_object().__class__.__name__,
  35. "kind": kind,
  36. "args": args,
  37. }
  38. current_app.queue.put(msg)
  39. @rpc.route("/")
  40. def rpc_index():
  41. rules = list(current_app.url_map.iter_rules())
  42. return jsonify({"rules": [r.rule for r in rules if r.rule.startswith("/api")]})
  43. @rpc.route("/refresh")
  44. def rpc_refresh():
  45. send_to_parent("refresh")
  46. return jsonify(dict(status="ok"))
  47. @rpc.route("/audiospec", methods=["GET"])
  48. def get_audiospec():
  49. return jsonify(current_app.larigira.controller.player.continous_audiospec)
  50. @rpc.route("/audiospec", methods=["PUT"])
  51. def change_audiospec():
  52. player = current_app.larigira.controller.player
  53. if request.json is None:
  54. abort(400, "Must send application/json data")
  55. if "spec" not in request.json or type(request.json["spec"]) is not dict:
  56. abort(400, "Object must have a key 'spec' whose value is an object")
  57. player.continous_audiospec = request.json["spec"]
  58. if "kind" not in request.json["spec"]:
  59. abort(400, "invalid audiospec")
  60. return jsonify(player.continous_audiospec)
  61. @rpc.route("/audiospec", methods=["DELETE"])
  62. def reset_audiospec():
  63. player = current_app.larigira.controller.player
  64. player.continous_audiospec = None
  65. return jsonify(player.continous_audiospec)
  66. @rpc.route("/eventsenabled/toggle", methods=["POST"])
  67. def toggle_events_enabled():
  68. status = current_app.larigira.controller.player.events_enabled
  69. current_app.larigira.controller.player.events_enabled = not status
  70. return jsonify(dict(events_enabled=not status))
  71. @rpc.route("/eventsenabled", methods=["GET"])
  72. def get_events_enabled():
  73. status = current_app.larigira.controller.player.events_enabled
  74. return jsonify(dict(events_enabled=status))
  75. @rpc.route("/eventsenabled", methods=["PUT"])
  76. def set_events_enabled():
  77. player = current_app.larigira.controller.player
  78. if request.json is None:
  79. abort(400, "Must send application/json data")
  80. if type(request.json) is not bool:
  81. abort(400, "Content must be a JSON boolean")
  82. player.events_enabled = request.json
  83. return jsonify(dict(events_enabled=request.json))
  84. def get_scheduled_audiogen():
  85. larigira = current_app.larigira
  86. running = larigira.controller.monitor.running
  87. events = {t: {} for t in running.keys()}
  88. for timespec_eid in events:
  89. orig_info = running[timespec_eid]
  90. info = events[timespec_eid]
  91. info["running_time"] = orig_info["running_time"].isoformat()
  92. info["audiospecs"] = orig_info["audiospecs"]
  93. info["timespec"] = orig_info["timespec"]
  94. info["timespec"]["actions"] = {
  95. aid: spec
  96. for aid, spec in zip(info["timespec"]["actions"], info["audiospecs"])
  97. }
  98. info["greenlet"] = hex(id(orig_info["greenlet"]))
  99. return events
  100. @viewui.route("/status/running")
  101. def ui_wip():
  102. audiogens = get_scheduled_audiogen()
  103. return render_template(
  104. "running.html",
  105. audiogens=sorted(audiogens.items(), key=lambda x: x[1]["running_time"]),
  106. )
  107. @rpc.route("/debug/running")
  108. def rpc_wip():
  109. def treeify(flat):
  110. roots = [obid for obid in flat if flat[obid]["parent"] not in flat]
  111. tree = deepcopy(flat)
  112. for obid in tree:
  113. tree[obid]["children"] = {}
  114. to_remove = []
  115. for obid in tree:
  116. if obid in roots:
  117. continue
  118. if obid not in tree:
  119. current_app.logger.warning("How strange, {} not in tree".format(obid))
  120. continue
  121. tree[tree[obid]["parent"]]["children"][obid] = tree[obid]
  122. to_remove.append(obid)
  123. for obid in to_remove:
  124. del tree[obid]
  125. return tree
  126. greenlets = {}
  127. for ob in filter(lambda obj: isinstance(obj, greenlet), gc.get_objects()):
  128. objrepr = {"repr": repr(ob), "class": ob.__class__.__name__}
  129. if hasattr(ob, "parent_greenlet") and ob.parent_greenlet is not None:
  130. objrepr["parent"] = hex(id(ob.parent_greenlet))
  131. else:
  132. objrepr["parent"] = hex(id(ob.parent)) if ob.parent is not None else None
  133. if hasattr(ob, "doc"):
  134. objrepr["doc"] = ob.doc.split("\n")[0]
  135. elif ob.__doc__:
  136. objrepr["doc"] = ob.__doc__.split("\n")[0]
  137. greenlets[hex(id(ob))] = objrepr
  138. # TODO: make it a tree
  139. return jsonify(
  140. dict(
  141. greenlets=greenlets,
  142. greenlets_tree=treeify(greenlets),
  143. audiogens=get_scheduled_audiogen(),
  144. )
  145. )
  146. def babel_get_locale():
  147. try:
  148. if request.accept_languages:
  149. return request.accept_languages[0][0]
  150. finally:
  151. return None
  152. def create_app(queue, larigira):
  153. app = Flask("larigira", static_url_path=get_conf()["ROUTE_PREFIX"] + "/static")
  154. app.config.update(get_conf())
  155. Bootstrap(app)
  156. babel = Babel(app)
  157. babel.localeselector(babel_get_locale)
  158. app.register_blueprint(rpc)
  159. app.register_blueprint(viewui)
  160. app.register_blueprint(db)
  161. app.route("/")(
  162. lambda: redirect(get_conf()["ROUTE_PREFIX"] + get_conf()["HOME_URL"])
  163. )
  164. app.queue = queue
  165. app.larigira = larigira
  166. app.cache = SimpleCache()
  167. return app