# 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")