From ad6da1438737280350f3beada00ea3a4421cfd67 Mon Sep 17 00:00:00 2001 From: Tim Van Baak Date: Fri, 2 Jun 2023 18:30:06 -0700 Subject: [PATCH] Add support for channels --- intake/app.py | 115 +++++++++++++++++++++++++++++------ intake/templates/edit.jinja2 | 13 +++- intake/templates/feed.jinja2 | 3 + intake/templates/home.jinja2 | 17 +++++- tests/channels.json | 7 +++ 5 files changed, 133 insertions(+), 22 deletions(-) create mode 100644 tests/channels.json diff --git a/intake/app.py b/intake/app.py index 95899c8..6d41ee7 100644 --- a/intake/app.py +++ b/intake/app.py @@ -1,5 +1,6 @@ from datetime import datetime, timedelta from pathlib import Path +from typing import List import json import os import time @@ -41,29 +42,62 @@ def root(): Navigation home page. """ data_path = intake_data_dir() + sources = [] for child in data_path.iterdir(): if (child / "intake.json").exists(): sources.append(LocalSource(data_path, child.name)) sources.sort(key=lambda s: s.source_name) + channels = {} + channels_config_path = data_path / "channels.json" + if channels_config_path.exists(): + channels = json.loads(channels_config_path.read_text(encoding="utf8")) + return render_template( "home.jinja2", sources=sources, + channels=channels, ) -@app.get("/source/") -def source_feed(source_name): +@app.get("/source/") +def source_feed(name): """ Feed view for a single source. """ - source = LocalSource(intake_data_dir(), source_name) + source = LocalSource(intake_data_dir(), name) if not source.source_path.exists(): abort(404) + return _sources_feed(name, [source]) + + +@app.get("/channel/") +def channel_feed(name): + """ + Feed view for a channel. + """ + channels_config_path = intake_data_dir() / "channels.json" + if not channels_config_path.exists(): + abort(404) + channels = json.loads(channels_config_path.read_text(encoding="utf8")) + if name not in channels: + abort(404) + + sources = [LocalSource(intake_data_dir(), name) for name in channels[name]] + return _sources_feed(name, sources) + + +def _sources_feed(name: str, sources: List[LocalSource]): + """ + Feed view for multiple sources. + """ # Get all items - all_items = sorted(source.get_all_items(), key=item_sort_key) + all_items = sorted( + [item for source in sources for item in source.get_all_items()], + key=item_sort_key, + ) # Apply paging parameters count = int(request.args.get("count", "100")) @@ -73,14 +107,14 @@ def source_feed(source_name): None if page <= 0 else url_for( - request.endpoint, source_name=source_name, count=count, page=page - 1 + request.endpoint, name=name, count=count, page=page - 1 ) ) pager_next = ( None if (count * page + count) > len(all_items) else url_for( - request.endpoint, source_name=source_name, count=count, page=page + 1 + request.endpoint, name=name, count=count, page=page + 1 ) ) @@ -147,12 +181,12 @@ def action(source_name, item_id, action): return jsonify(item) -@app.route("/edit/source/", methods=["GET", "POST"]) -def source_edit(source_name): +@app.route("/edit/source/", methods=["GET", "POST"]) +def source_edit(name): """ Config editor for a source """ - source = LocalSource(intake_data_dir(), source_name) + source = LocalSource(intake_data_dir(), name) if not source.source_path.exists(): abort(404) @@ -160,10 +194,7 @@ def source_edit(source_name): error_message: str = None if request.method == "POST": config_str = request.form.get("config", "") - error_message, config = try_parse_config(config_str) - print(config_str) - print(error_message) - print(config) + error_message, config = _parse_source_config(config_str) if not error_message: source.save_config(config) return redirect(url_for("root")) @@ -175,13 +206,13 @@ def source_edit(source_name): return render_template( "edit.jinja2", - source=source, + subtitle=source.source_name, config=config_str, error_message=error_message, ) -def try_parse_config(config_str: str): +def _parse_source_config(config_str: str): if not config_str: return ("Config required", {}) try: @@ -198,14 +229,62 @@ def try_parse_config(config_str: str): fetch = action["fetch"] if "exe" not in fetch: return ("No fetch exe", {}) - config = { - "action": parsed["action"] - } + config = {"action": parsed["action"]} if "env" in parsed: config["env"] = parsed["env"] return (None, config) +@app.route("/edit/channels", methods=["GET", "POST"]) +def channels_edit(): + """ + Config editor for channels + """ + config_path = intake_data_dir() / "channels.json" + + # For POST, check if the config is valid + error_message: str = None + if request.method == "POST": + config_str = request.form.get("config", "") + error_message, config = _parse_channels_config(config_str) + if not error_message: + config_path.write_text(json.dumps(config, indent=2), encoding="utf8") + return redirect(url_for("root")) + + # For GET, load the config + if request.method == "GET": + if config_path.exists(): + config = json.loads(config_path.read_text(encoding="utf8")) + else: + config = {} + config_str = json.dumps(config, indent=2) + + return render_template( + "edit.jinja2", + subtitle="Channels", + config=config_str, + error_message=error_message, + ) + + +def _parse_channels_config(config_str: str): + if not config_str: + return ("Config required", {}) + try: + parsed = json.loads(config_str) + except json.JSONDecodeError: + return ("Invalid JSON", {}) + if not isinstance(parsed, dict): + return ("Invalid config format", {}) + for key in parsed: + if not isinstance(parsed[key], list): + return (f"{key} must map to a list", {}) + for val in parsed[key]: + if not isinstance(val, str): + return f"{key} source {val} must be a string" + return (None, parsed) + + def wsgi(): # init_default_logging() return app diff --git a/intake/templates/edit.jinja2 b/intake/templates/edit.jinja2 index 56e2a85..88fa370 100644 --- a/intake/templates/edit.jinja2 +++ b/intake/templates/edit.jinja2 @@ -1,7 +1,7 @@ -Intake - {{ source.source_name }} +Intake - {{ subtitle }} @@ -68,9 +71,13 @@ table.feed-control td {
- + -

+

+{% if error_message %} +{{ error_message }} +{% endif %} +

diff --git a/intake/templates/feed.jinja2 b/intake/templates/feed.jinja2 index afc8714..bc10078 100644 --- a/intake/templates/feed.jinja2 +++ b/intake/templates/feed.jinja2 @@ -121,6 +121,9 @@ var doAction = function (source, itemid, action) {
+
+Home +
{% if items %} {% for item in items %}
diff --git a/intake/templates/home.jinja2 b/intake/templates/home.jinja2 index f4ada70..422efea 100644 --- a/intake/templates/home.jinja2 +++ b/intake/templates/home.jinja2 @@ -33,6 +33,21 @@ summary:focus {
+ +
+
+Channels +{% if not channels %} +

No channels found.

+{% else %} +{% for channel in channels %} +

{{ channel }}

+{% endfor %} +{% endif %} +

Edit channels

+
+
+
Sources @@ -40,7 +55,7 @@ summary:focus {

No sources found.

{% else %} {% for source in sources %} -

{{ source.source_name|safe }} (edit)

+

{{ source.source_name|safe }} (edit)

{% endfor %} {% endif %}
diff --git a/tests/channels.json b/tests/channels.json new file mode 100644 index 0000000..0d2c6bc --- /dev/null +++ b/tests/channels.json @@ -0,0 +1,7 @@ +{ + "demo": [ + "demo_basic_callback", + "demo_logging", + "demo_raw_sh" + ] +} \ No newline at end of file