summaryrefslogtreecommitdiff
path: root/utab/__main__.py
diff options
context:
space:
mode:
authorFrederick Yin <fkfd@macaw.me>2020-07-04 22:12:11 +0800
committerFrederick Yin <fkfd@macaw.me>2020-07-04 22:12:11 +0800
commit9527f0b05945871190e1592086e1d48e134dbaa5 (patch)
tree49a4bef40b10d9bf44b19ee3e7c05c885c1d154b /utab/__main__.py
parent0be69d4b5db18b1e0794fd3dc3297da0a16b1ccf (diff)
Add or edit site
Diffstat (limited to 'utab/__main__.py')
-rw-r--r--utab/__main__.py146
1 files changed, 133 insertions, 13 deletions
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/<escaped_url>
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/<path:url>")
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=<escaped_url>&title=<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)