# HG changeset patch # User paulo # Date 1600416809 25200 # Node ID 65db090a697e9933832f30c0793772dc75192d3b # Parent 476cb019ad9fd10f26ad912967875e464a308b3f laterlinks3: add GCS support diff -r 476cb019ad9f -r 65db090a697e laterlinks3/Dockerfile --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/laterlinks3/Dockerfile Fri Sep 18 01:13:29 2020 -0700 @@ -0,0 +1,17 @@ +# Use the official lightweight Python image. +# https://hub.docker.com/_/python +FROM python:3.6-slim + +# Copy local code to the container image. +ENV APP_HOME /app +WORKDIR $APP_HOME +COPY . ./ + +# Install production dependencies. +RUN pip install -r requirements.pip + +# Run the web service on container startup. Here we use the gunicorn +# webserver, with one worker process and 8 threads. +# For environments with multiple CPU cores, increase the number of workers +# to be equal to the cores available. +CMD exec gunicorn --bind :$PORT --workers 1 --threads 8 --timeout 0 laterlinks_flask_app:app diff -r 476cb019ad9f -r 65db090a697e laterlinks3/laterlinks_flask_app.py --- a/laterlinks3/laterlinks_flask_app.py Tue Sep 08 23:52:57 2020 -0700 +++ b/laterlinks3/laterlinks_flask_app.py Fri Sep 18 01:13:29 2020 -0700 @@ -1,14 +1,17 @@ import csv import datetime +import io import os +import tempfile import flask - +import google.cloud.storage from html3.html3 import HTML app = flask.Flask(__name__) -PIN = os.environ.get('LLPIN') +GCS_BUCKET = google.cloud.storage.Client().get_bucket(os.environ.get("GCS_BUCKET")) +PIN = os.environ.get("LLPIN") STRTIME_FMT = "%Y-%m-%d %H:%M:%S" @@ -39,30 +42,47 @@ return "MISSING FIELD(s)!" -def lldb_unread_load(): - return csv.reader(open(LLDB_UNREAD_FN), LLDIALECT) +def gcs_download(fn): + tmp_f = tempfile.TemporaryFile("w+") + blob = GCS_BUCKET.get_blob(fn) + if blob: + tmp_f.write(str(blob.download_as_string(), encoding="utf-8")) + return tmp_f -def lldb_add(inp): +def gcs_upload(fn, tmp_f): + blob = GCS_BUCKET.blob(fn) + blob.upload_from_file(tmp_f, rewind=True) + + +def lldb_unread_load(lldb_unread_tmp_f): + lldb_unread_tmp_f.seek(0) + return csv.reader(lldb_unread_tmp_f, LLDIALECT) + + +def lldb_add(inp, lldb_tmp_f, lldb_unread_tmp_f): title = inp.get("title") url = inp.get("url") if not (title and url): raise MissingFieldsError() dt_str = datetime.datetime.now().strftime(STRTIME_FMT) - with open(LLDB_FN, 'a') as lldb_f: - csv.writer(lldb_f, LLDIALECT).writerow([title, url, dt_str]) - with open(LLDB_UNREAD_FN, 'a') as lldb_f: - csv.writer(lldb_f, LLDIALECT).writerow([title, url, dt_str]) + lldb_tmp_f.seek(0, io.SEEK_END) + csv.writer(lldb_tmp_f, LLDIALECT).writerow([title, url, dt_str]) + gcs_upload(LLDB_FN, lldb_tmp_f) -def lldb_unread_delete(inp): + lldb_unread_tmp_f.seek(0, io.SEEK_END) + csv.writer(lldb_unread_tmp_f, LLDIALECT).writerow([title, url, dt_str]) + gcs_upload(LLDB_UNREAD_FN, lldb_unread_tmp_f) + + +def lldb_unread_delete(inp, lldb_unread_tmp_f): delete = inp.getlist("delete") if not delete: raise MissingFieldsError() - lldb_unread = [i for i in lldb_unread_load()] - lldb_unread_f = open(LLDB_UNREAD_FN, 'w') + lldb_unread = [i for i in lldb_unread_load(lldb_unread_tmp_f)] try: for i in delete: @@ -71,9 +91,10 @@ if i == dt_str: lldb_unread.remove(j) finally: - csv.writer(lldb_unread_f, LLDIALECT).writerows(lldb_unread) - lldb_unread_f.close() - + lldb_unread_tmp_f.truncate(0) + lldb_unread_tmp_f.seek(0) + csv.writer(lldb_unread_tmp_f, LLDIALECT).writerows(lldb_unread) + gcs_upload(LLDB_UNREAD_FN, lldb_unread_tmp_f) @app.route("/", methods=["GET", "POST"]) @@ -82,46 +103,48 @@ inp = flask.request.form cookies = flask.request.cookies - if is_post: - if not PIN: - raise PinSetupError - elif cookies.get("llpin") != PIN: - raise PinFailError + with gcs_download(LLDB_UNREAD_FN) as lldb_unread_tmp_f: + if is_post: + if not PIN: + raise PinSetupError + elif cookies.get("llpin") != PIN: + raise PinFailError - if inp["submit"] == "Add": - lldb_add(inp) - elif inp["submit"] == "Delete": - lldb_unread_delete(inp) + if inp["submit"] == "Add": + with gcs_download(LLDB_FN) as lldb_tmp_f: + lldb_add(inp, lldb_tmp_f, lldb_unread_tmp_f) + elif inp["submit"] == "Delete": + lldb_unread_delete(inp, lldb_unread_tmp_f) - title = "later links..." - root = HTML("html") + title = "later links..." + root = HTML("html") - header = root.head - header.link(rel="stylesheet", type="text/css", href=flask.url_for("static", filename="index.css")) - header.title(title) - - body = root.body - body.h1(title) + header = root.head + header.link(rel="stylesheet", type="text/css", href=flask.url_for("static", filename="index.css")) + header.title(title) + + body = root.body + body.h1(title) - form = body.form(action="/", method="post") + form = body.form(action="/", method="post") - table = form.table - hrow = table.tr - hrow.th("Link") - hrow.th("Created") - hrow.th.input(type="submit", name="submit", value="Delete") + table = form.table + hrow = table.tr + hrow.th("Link") + hrow.th("Created") + hrow.th.input(type="submit", name="submit", value="Delete") - for (title, url, dt_str) in lldb_unread_load(): - row = table.tr - row.td.a(title, href=url) - row.td(dt_str) - row.td.input(type="checkbox", name="delete", value=dt_str) + for (title, url, dt_str) in lldb_unread_load(lldb_unread_tmp_f): + row = table.tr + row.td.a(title, href=url) + row.td(dt_str) + row.td.input(type="checkbox", name="delete", value=dt_str) - p1 = form.p - p1.label("Title").input(type="text", name="title", size="64") - p1.br - p1.label("URL").input(type="text", name="url", size="64") - p1.br - p1.input(type="submit", name="submit", value="Add") + p1 = form.p + p1.label("Title").input(type="text", name="title", size="64") + p1.br + p1.label("URL").input(type="text", name="url", size="64") + p1.br + p1.input(type="submit", name="submit", value="Add") - return str(root).encode("utf-8") + return str(root).encode("utf-8")