rev |
line source |
paulo@117
|
1 import csv
|
paulo@117
|
2 import datetime
|
paulo@118
|
3 import io
|
paulo@117
|
4 import os
|
paulo@118
|
5 import tempfile
|
paulo@117
|
6
|
paulo@117
|
7 import flask
|
paulo@118
|
8 import google.cloud.storage
|
paulo@117
|
9 from html3.html3 import HTML
|
paulo@117
|
10
|
paulo@117
|
11 app = flask.Flask(__name__)
|
paulo@117
|
12
|
paulo@118
|
13 GCS_BUCKET = google.cloud.storage.Client().get_bucket(os.environ.get("GCS_BUCKET"))
|
paulo@118
|
14 PIN = os.environ.get("LLPIN")
|
paulo@117
|
15 STRTIME_FMT = "%Y-%m-%d %H:%M:%S"
|
paulo@117
|
16
|
paulo@117
|
17
|
paulo@117
|
18 class LLDialect(csv.Dialect):
|
paulo@117
|
19 delimiter = '\t'
|
paulo@117
|
20 quoting = csv.QUOTE_NONE
|
paulo@117
|
21 lineterminator = '\n'
|
paulo@117
|
22
|
paulo@117
|
23
|
paulo@117
|
24 LLDIALECT = LLDialect()
|
paulo@117
|
25 LLDB_FN = "lldb.tsv"
|
paulo@117
|
26 LLDB_UNREAD_FN = "lldb_unread.tsv"
|
paulo@117
|
27
|
paulo@117
|
28
|
paulo@117
|
29 class LLError(Exception):
|
paulo@117
|
30 pass
|
paulo@117
|
31
|
paulo@117
|
32 class PinFailError(LLError):
|
paulo@117
|
33 def __str__(self):
|
paulo@117
|
34 return "PIN FAIL!"
|
paulo@117
|
35
|
paulo@117
|
36 class PinSetupError(LLError):
|
paulo@117
|
37 def __str__(self):
|
paulo@117
|
38 return "PIN SETUP ERROR!"
|
paulo@117
|
39
|
paulo@117
|
40 class MissingFieldsError(LLError):
|
paulo@117
|
41 def __str__(self):
|
paulo@117
|
42 return "MISSING FIELD(s)!"
|
paulo@117
|
43
|
paulo@117
|
44
|
paulo@118
|
45 def gcs_download(fn):
|
paulo@118
|
46 tmp_f = tempfile.TemporaryFile("w+")
|
paulo@118
|
47 blob = GCS_BUCKET.get_blob(fn)
|
paulo@118
|
48 if blob:
|
paulo@118
|
49 tmp_f.write(str(blob.download_as_string(), encoding="utf-8"))
|
paulo@118
|
50 return tmp_f
|
paulo@117
|
51
|
paulo@117
|
52
|
paulo@118
|
53 def gcs_upload(fn, tmp_f):
|
paulo@118
|
54 blob = GCS_BUCKET.blob(fn)
|
paulo@121
|
55 tmp_f.seek(0)
|
paulo@121
|
56 blob.upload_from_string(tmp_f.read())
|
paulo@118
|
57
|
paulo@118
|
58
|
paulo@118
|
59 def lldb_unread_load(lldb_unread_tmp_f):
|
paulo@118
|
60 lldb_unread_tmp_f.seek(0)
|
paulo@118
|
61 return csv.reader(lldb_unread_tmp_f, LLDIALECT)
|
paulo@118
|
62
|
paulo@118
|
63
|
paulo@118
|
64 def lldb_add(inp, lldb_tmp_f, lldb_unread_tmp_f):
|
paulo@117
|
65 title = inp.get("title")
|
paulo@117
|
66 url = inp.get("url")
|
paulo@117
|
67 if not (title and url):
|
paulo@117
|
68 raise MissingFieldsError()
|
paulo@117
|
69
|
paulo@117
|
70 dt_str = datetime.datetime.now().strftime(STRTIME_FMT)
|
paulo@117
|
71
|
paulo@118
|
72 lldb_tmp_f.seek(0, io.SEEK_END)
|
paulo@118
|
73 csv.writer(lldb_tmp_f, LLDIALECT).writerow([title, url, dt_str])
|
paulo@118
|
74 gcs_upload(LLDB_FN, lldb_tmp_f)
|
paulo@117
|
75
|
paulo@118
|
76 lldb_unread_tmp_f.seek(0, io.SEEK_END)
|
paulo@118
|
77 csv.writer(lldb_unread_tmp_f, LLDIALECT).writerow([title, url, dt_str])
|
paulo@118
|
78 gcs_upload(LLDB_UNREAD_FN, lldb_unread_tmp_f)
|
paulo@118
|
79
|
paulo@118
|
80
|
paulo@118
|
81 def lldb_unread_delete(inp, lldb_unread_tmp_f):
|
paulo@117
|
82 delete = inp.getlist("delete")
|
paulo@117
|
83 if not delete:
|
paulo@117
|
84 raise MissingFieldsError()
|
paulo@117
|
85
|
paulo@118
|
86 lldb_unread = [i for i in lldb_unread_load(lldb_unread_tmp_f)]
|
paulo@117
|
87
|
paulo@117
|
88 try:
|
paulo@117
|
89 for i in delete:
|
paulo@117
|
90 for j in lldb_unread:
|
paulo@117
|
91 dt_str = j[2]
|
paulo@117
|
92 if i == dt_str:
|
paulo@117
|
93 lldb_unread.remove(j)
|
paulo@117
|
94 finally:
|
paulo@118
|
95 lldb_unread_tmp_f.truncate(0)
|
paulo@118
|
96 lldb_unread_tmp_f.seek(0)
|
paulo@118
|
97 csv.writer(lldb_unread_tmp_f, LLDIALECT).writerows(lldb_unread)
|
paulo@118
|
98 gcs_upload(LLDB_UNREAD_FN, lldb_unread_tmp_f)
|
paulo@117
|
99
|
paulo@117
|
100
|
paulo@117
|
101 @app.route("/", methods=["GET", "POST"])
|
paulo@117
|
102 def index():
|
paulo@117
|
103 is_post = (flask.request.method == "POST")
|
paulo@117
|
104 inp = flask.request.form
|
paulo@117
|
105 cookies = flask.request.cookies
|
paulo@117
|
106
|
paulo@118
|
107 with gcs_download(LLDB_UNREAD_FN) as lldb_unread_tmp_f:
|
paulo@118
|
108 if is_post:
|
paulo@118
|
109 if not PIN:
|
paulo@118
|
110 raise PinSetupError
|
paulo@118
|
111 elif cookies.get("llpin") != PIN:
|
paulo@118
|
112 raise PinFailError
|
paulo@117
|
113
|
paulo@118
|
114 if inp["submit"] == "Add":
|
paulo@118
|
115 with gcs_download(LLDB_FN) as lldb_tmp_f:
|
paulo@118
|
116 lldb_add(inp, lldb_tmp_f, lldb_unread_tmp_f)
|
paulo@118
|
117 elif inp["submit"] == "Delete":
|
paulo@118
|
118 lldb_unread_delete(inp, lldb_unread_tmp_f)
|
paulo@117
|
119
|
paulo@118
|
120 title = "later links..."
|
paulo@118
|
121 root = HTML("html")
|
paulo@117
|
122
|
paulo@118
|
123 header = root.head
|
paulo@118
|
124 header.link(rel="stylesheet", type="text/css", href=flask.url_for("static", filename="index.css"))
|
paulo@118
|
125 header.title(title)
|
paulo@118
|
126
|
paulo@118
|
127 body = root.body
|
paulo@118
|
128 body.h1(title)
|
paulo@117
|
129
|
paulo@118
|
130 form = body.form(action="/", method="post")
|
paulo@117
|
131
|
paulo@118
|
132 table = form.table
|
paulo@118
|
133 hrow = table.tr
|
paulo@118
|
134 hrow.th("Link")
|
paulo@118
|
135 hrow.th("Created")
|
paulo@118
|
136 hrow.th.input(type="submit", name="submit", value="Delete")
|
paulo@117
|
137
|
paulo@118
|
138 for (title, url, dt_str) in lldb_unread_load(lldb_unread_tmp_f):
|
paulo@118
|
139 row = table.tr
|
paulo@118
|
140 row.td.a(title, href=url)
|
paulo@118
|
141 row.td(dt_str)
|
paulo@118
|
142 row.td.input(type="checkbox", name="delete", value=dt_str)
|
paulo@117
|
143
|
paulo@118
|
144 p1 = form.p
|
paulo@118
|
145 p1.label("Title").input(type="text", name="title", size="64")
|
paulo@118
|
146 p1.br
|
paulo@118
|
147 p1.label("URL").input(type="text", name="url", size="64")
|
paulo@118
|
148 p1.br
|
paulo@118
|
149 p1.input(type="submit", name="submit", value="Add")
|
paulo@117
|
150
|
paulo@118
|
151 return str(root).encode("utf-8")
|