From 9527f0b05945871190e1592086e1d48e134dbaa5 Mon Sep 17 00:00:00 2001 From: Frederick Yin Date: Sat, 4 Jul 2020 22:12:11 +0800 Subject: Add or edit site --- utab/__main__.py | 146 ++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 133 insertions(+), 13 deletions(-) (limited to 'utab/__main__.py') diff --git a/utab/__main__.py b/utab/__main__.py index 5e619ed..df92dbc 100644 --- a/utab/__main__.py +++ b/utab/__main__.py @@ -2,9 +2,11 @@ from flask import Flask, Response, request, redirect, abort from appdirs import user_data_dir from pathlib import Path import urllib +from mimetypes import guess_type import sys import yaml import csv +from .pyfav import get_favicon_url from .rendering import * app = Flask(__name__) @@ -13,19 +15,29 @@ app = Flask(__name__) # and sites json file at utab/sites.json data_dir = user_data_dir(appname="utab") template_fp = Path(data_dir) / "index.html" +site_form_fp = Path(data_dir) / "site.html" css_dir = Path(data_dir) / "css" +icons_dir = Path(data_dir) / "icons" try: - template = Path.open(template_fp).read() + with open(template_fp) as f: + template = f.read() + f.close() + with open(site_form_fp) as f: + site_form = f.read() + f.close() except FileNotFoundError: - print("Template file not found.") + print("One or more template files are not found.") sys.exit(1) + sites_fp = Path(data_dir) / "sites.csv" config_fp = Path(data_dir) / "config.yml" with open(config_fp) as f: - config = yaml.load(f.read()) + config = yaml.load(f.read(), Loader=yaml.FullLoader) f.close() +sites_grid_dimensions = {"columns": config["columns"], "rows": config["rows"]} + def read_sites(): with open(sites_fp) as f: @@ -34,34 +46,128 @@ def read_sites(): return sites +def write_sites(data): + with open(sites_fp, "w") as f: + csv.writer(f).writerows(data) + f.close() + + +def append_site(site): + with open(sites_fp, "a") as f: + csv.writer(f).writerow(site) + f.close() + + @app.route("/") def index(): + # render sites in a grid. + # each cell links to /go/ return render_page( template, - sites=render_sites( - read_sites(), columns=config["columns"], rows=config["rows"] - ), + site_heading="Top Sites", + sites=render_sites(read_sites(), **sites_grid_dimensions), ) @app.route("/go/") def visit_site(url): - print(url) + # log this visit, then redirect to the unescaped url url_unesc = urllib.parse.unquote(url) # unescaped url - print(url_unesc) sites = read_sites() for i, s in enumerate(sites): - if s[0] == url_unesc: + if s[URL] == url_unesc: sites[i][VISITS] = str(int(sites[i][VISITS]) + 1) - with open(sites_fp, "w") as f: - # update visits - csv.writer(f).writerows(sites) - f.close() + write_sites(sites) return redirect(url_unesc, 302) + return abort(404) + + +@app.route("/new") +def add_site(): + # /new => a page asking for url, title, etc. + # /new?url=&title=&... => add this site if it DNE, else abort + url, title, favicon = ( + request.args.get("url"), + request.args.get("title"), + request.args.get("favicon"), + ) + if url: + sites = read_sites() + for s in sites: + if s[URL] == url: + return "A site with the same URL already exists.", 403 + # now we have ensured there isn't such URL in sites + if favicon: + favicon_src = favicon + else: + # get its favicon url + # fetch favicon url from the root page (w/o path): + url_split = urllib.parse.urlsplit(url) + root = url_split.scheme + "://" + url_split.netloc + favicon_src = get_favicon_url(root) + + append_site([url, title, favicon_src, 0]) + return redirect("/", 302) + else: # no request params; let user fill form + return render_page( + site_form, + site_heading="Add site", + url="https://", + title="", + favicon_src="", + favicon_placeholder="Leave blank to auto-retrieve favicon. Base64 is allowed.", + action="new", + ) + + +@app.route("/edit/<path:url>") +def edit_site(url): + # /edit/ => /edit + # /edit/<escaped_url> => form with original data pre-filled for user to modify + # /edit/<escaped_url>?url=<escaped_new_url>&... => edit this site in database + if not url: + return redirect("/edit", 301) + url_unesc = urllib.parse.unquote(url) + sites = read_sites() + for i, s in enumerate(sites): + if s[URL] == url_unesc: + new_url, new_title, new_favicon = ( + request.args.get("url"), + request.args.get("title"), + request.args.get("favicon"), + ) + if not new_url: + return render_page( + site_form, + site_heading="Edit site", + url=s[URL], + title=s[TITLE], + favicon_src=s[FAVICON], + favicon_placeholder="Base64 is allowed.", + action="edit/" + urllib.parse.quote(s[URL], safe=""), + ) + sites[i][URL] = new_url + sites[i][TITLE] = new_title + sites[i][FAVICON] = new_favicon + write_sites(sites) + return redirect("/", 302) + return abort(404) + + +@app.route("/edit") +def select_site_to_edit(): + # render sites in a grid. + # each cell links to /edit/<escaped_url> + return render_page( + template, + site_heading="Select site to edit", + sites=render_sites(read_sites(), action="edit", **sites_grid_dimensions), + ) @app.route("/css/<string:filename>") def serve_css(filename): + # serve static CSS because browsers forbid file:// scheme try: with open(css_dir / filename) as f: resp = Response(f.read(), 200, {"Content-Type": "text/css"}) @@ -73,5 +179,19 @@ def serve_css(filename): return abort(500) +@app.route("/icons/<string:filename>") +def serve_icon(filename): + try: + fp = icons_dir / filename + with open(fp, "rb") as f: + resp = Response(f.read(), 200, {"Content-Type": guess_type(fp)[0]}) + f.close() + return resp + except FileNotFoundError: + return abort(404) + except: + return abort(500) + + # run on localhost only app.run("127.0.0.1", 64366) -- cgit v1.2.3