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@118
|
55 blob.upload_from_file(tmp_f, rewind=True)
|
paulo@118
|
56
|
paulo@118
|
57
|
paulo@118
|
58 def lldb_unread_load(lldb_unread_tmp_f):
|
paulo@118
|
59 lldb_unread_tmp_f.seek(0)
|
paulo@118
|
60 return csv.reader(lldb_unread_tmp_f, LLDIALECT)
|
paulo@118
|
61
|
paulo@118
|
62
|
paulo@118
|
63 def lldb_add(inp, lldb_tmp_f, lldb_unread_tmp_f):
|
paulo@117
|
64 title = inp.get("title")
|
paulo@117
|
65 url = inp.get("url")
|
paulo@117
|
66 if not (title and url):
|
paulo@117
|
67 raise MissingFieldsError()
|
paulo@117
|
68
|
paulo@117
|
69 dt_str = datetime.datetime.now().strftime(STRTIME_FMT)
|
paulo@117
|
70
|
paulo@118
|
71 lldb_tmp_f.seek(0, io.SEEK_END)
|
paulo@118
|
72 csv.writer(lldb_tmp_f, LLDIALECT).writerow([title, url, dt_str])
|
paulo@118
|
73 gcs_upload(LLDB_FN, lldb_tmp_f)
|
paulo@117
|
74
|
paulo@118
|
75 lldb_unread_tmp_f.seek(0, io.SEEK_END)
|
paulo@118
|
76 csv.writer(lldb_unread_tmp_f, LLDIALECT).writerow([title, url, dt_str])
|
paulo@118
|
77 gcs_upload(LLDB_UNREAD_FN, lldb_unread_tmp_f)
|
paulo@118
|
78
|
paulo@118
|
79
|
paulo@118
|
80 def lldb_unread_delete(inp, lldb_unread_tmp_f):
|
paulo@117
|
81 delete = inp.getlist("delete")
|
paulo@117
|
82 if not delete:
|
paulo@117
|
83 raise MissingFieldsError()
|
paulo@117
|
84
|
paulo@118
|
85 lldb_unread = [i for i in lldb_unread_load(lldb_unread_tmp_f)]
|
paulo@117
|
86
|
paulo@117
|
87 try:
|
paulo@117
|
88 for i in delete:
|
paulo@117
|
89 for j in lldb_unread:
|
paulo@117
|
90 dt_str = j[2]
|
paulo@117
|
91 if i == dt_str:
|
paulo@117
|
92 lldb_unread.remove(j)
|
paulo@117
|
93 finally:
|
paulo@118
|
94 lldb_unread_tmp_f.truncate(0)
|
paulo@118
|
95 lldb_unread_tmp_f.seek(0)
|
paulo@118
|
96 csv.writer(lldb_unread_tmp_f, LLDIALECT).writerows(lldb_unread)
|
paulo@118
|
97 gcs_upload(LLDB_UNREAD_FN, lldb_unread_tmp_f)
|
paulo@117
|
98
|
paulo@117
|
99
|
paulo@117
|
100 @app.route("/", methods=["GET", "POST"])
|
paulo@117
|
101 def index():
|
paulo@117
|
102 is_post = (flask.request.method == "POST")
|
paulo@117
|
103 inp = flask.request.form
|
paulo@117
|
104 cookies = flask.request.cookies
|
paulo@117
|
105
|
paulo@118
|
106 with gcs_download(LLDB_UNREAD_FN) as lldb_unread_tmp_f:
|
paulo@118
|
107 if is_post:
|
paulo@118
|
108 if not PIN:
|
paulo@118
|
109 raise PinSetupError
|
paulo@118
|
110 elif cookies.get("llpin") != PIN:
|
paulo@118
|
111 raise PinFailError
|
paulo@117
|
112
|
paulo@118
|
113 if inp["submit"] == "Add":
|
paulo@118
|
114 with gcs_download(LLDB_FN) as lldb_tmp_f:
|
paulo@118
|
115 lldb_add(inp, lldb_tmp_f, lldb_unread_tmp_f)
|
paulo@118
|
116 elif inp["submit"] == "Delete":
|
paulo@118
|
117 lldb_unread_delete(inp, lldb_unread_tmp_f)
|
paulo@117
|
118
|
paulo@118
|
119 title = "later links..."
|
paulo@118
|
120 root = HTML("html")
|
paulo@117
|
121
|
paulo@118
|
122 header = root.head
|
paulo@118
|
123 header.link(rel="stylesheet", type="text/css", href=flask.url_for("static", filename="index.css"))
|
paulo@118
|
124 header.title(title)
|
paulo@118
|
125
|
paulo@118
|
126 body = root.body
|
paulo@118
|
127 body.h1(title)
|
paulo@117
|
128
|
paulo@118
|
129 form = body.form(action="/", method="post")
|
paulo@117
|
130
|
paulo@118
|
131 table = form.table
|
paulo@118
|
132 hrow = table.tr
|
paulo@118
|
133 hrow.th("Link")
|
paulo@118
|
134 hrow.th("Created")
|
paulo@118
|
135 hrow.th.input(type="submit", name="submit", value="Delete")
|
paulo@117
|
136
|
paulo@118
|
137 for (title, url, dt_str) in lldb_unread_load(lldb_unread_tmp_f):
|
paulo@118
|
138 row = table.tr
|
paulo@118
|
139 row.td.a(title, href=url)
|
paulo@118
|
140 row.td(dt_str)
|
paulo@118
|
141 row.td.input(type="checkbox", name="delete", value=dt_str)
|
paulo@117
|
142
|
paulo@118
|
143 p1 = form.p
|
paulo@118
|
144 p1.label("Title").input(type="text", name="title", size="64")
|
paulo@118
|
145 p1.br
|
paulo@118
|
146 p1.label("URL").input(type="text", name="url", size="64")
|
paulo@118
|
147 p1.br
|
paulo@118
|
148 p1.input(type="submit", name="submit", value="Add")
|
paulo@117
|
149
|
paulo@118
|
150 return str(root).encode("utf-8")
|