-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathapp.py
executable file
·260 lines (229 loc) · 7.84 KB
/
app.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
#!/usr/bin/env python
import gzip_middleware
import bottle as b
import subprocess
import threading
import functools
import setup_db
import datetime
import binascii
import sqlite3
import hashlib
import pbkdf2
import time
import os
import conf
#copied from sqlite3/dbapi2.py
def convert_timestamp(val):
datepart, timepart = val.split(" ")
year, month, day = map(int, datepart.split("-"))
timepart_full = timepart.split(".")
hours, minutes, seconds = map(int, timepart_full[0].split(":"))
if len(timepart_full) == 2:
microseconds = int('{:0<6.6}'.format(timepart_full[1].decode()))
else:
microseconds = 0
val = datetime.datetime(year, month, day, hours, minutes, seconds, microseconds)
return val
class Feed:
def __init__(self, f_id, title, feed_url, site_url, active, items, sort_index):
self.feed_id = f_id
self.title = title
self.items = items
self.sort_index = sort_index
self.feed_url = feed_url
self.site_url = site_url
self.active = active
class FeedItem:
def __init__(self, db_id, title, link, feed, item_id, date):
self.title = title
self.link = link
self.feed = feed
self.item_id = item_id
self.date = convert_timestamp(date)
self.db_id = db_id
valid_auth_cookies = set()
def auth_required(view):
@functools.wraps(view)
def auth_view(*args, **kargs):
auth_cookie = b.request.get_cookie("auth")
if auth_cookie in valid_auth_cookies:
return view(*args, **kargs)
b.redirect(conf.prefix + "/auth", 307)
return auth_view
def cursor():
db = sqlite3.connect(conf.database)
db.row_factory = sqlite3.Row
return db.cursor(), db
@b.route(conf.prefix + "/auth")
@b.view("auth")
def auth():
return {'c': conf}
@b.post(conf.prefix + "/submit_auth")
def submit_auth():
password = b.request.forms.get('password')
c, db = cursor()
c.execute("SELECT * from passwords WHERE user = ?", ("user",))
pw_hash = c.fetchone()['password']
if pbkdf2.check_hash(password, pw_hash):
auth_token = binascii.b2a_hex(os.urandom(16))
valid_auth_cookies.add(auth_token)
b.response.add_header('Set-Cookie', 'auth={}'.format(auth_token))
b.redirect(conf.prefix + "/unread")
else:
b.redirect(conf.prefix + "/auth")
@b.route(conf.prefix + "/")
@b.view("index")
@auth_required
def index():
b.redirect(conf.prefix + "/unread")
return {}
@b.route(conf.prefix + "/new")
@b.view("new")
@auth_required
def new():
return {'c': conf}
@b.route(conf.prefix + "/unread")
@b.view("unread")
@auth_required
def unread():
global fetch_process
c, db = cursor()
c.execute("""SELECT * FROM feed_items JOIN feeds
ON feed_items.feed = feeds.id
WHERE feed_items.read = 0
ORDER BY date(feed_items.date) DESC LIMIT 500""")
unread_feeds = {}
for i, row in enumerate(c):
feed = unread_feeds.get(row['feed'], Feed(row['feed'],
row['name'],
row['feed_uri'],
row['site_uri'],
row['active'],
[], i))
feed.items.append(FeedItem(row['id'],
row['title'],
row['link'],
row['feed'],
row['item_id'],
row['date']))
unread_feeds[row['feed']] = feed
unread_feeds = unread_feeds.values()
unread_feeds.sort(key=lambda x: x.sort_index)
refreshing = fetch_process is not None and fetch_process.poll() is None
return {'c': conf, 'unread_feeds': unread_feeds, 'refreshing' : refreshing}
@b.route(conf.prefix + "/feed/<feed_id>")
@b.view("feed")
@auth_required
def feed(feed_id):
c, db = cursor()
c.execute("""SELECT * FROM feeds WHERE ID = ?""", (feed_id,))
row = c.fetchone()
if not row:
b.abort(404, "No such feed")
feed = Feed(row['id'],
row['name'],
row['feed_uri'],
row['site_uri'],
row['active'],
[], None)
c.execute("""SELECT * FROM feed_items WHERE feed = ?
ORDER BY date(feed_items.date) DESC LIMIT 200""", (feed_id,))
for row in c:
feed.items.append(FeedItem(row['id'],
row['title'],
row['link'],
row['feed'],
row['item_id'],
row['date']))
return {'c':conf, 'feed' : feed}
@b.route(conf.prefix + "/status")
@b.view("status")
@auth_required
def status():
c, db = cursor()
c.execute("""SELECT * FROM feed_status JOIN feeds
ON feed_status.feed = feeds.id
ORDER BY feeds.active, feed_status.last_error DESC""")
return {'c': conf, 'statuses': c.fetchall()}
@b.post(conf.prefix + "/mark_read")
@auth_required
def mark_read():
c, db = cursor()
feed_ids = [(i,) for i in b.request.forms.get("feed_id").split(",")]
c.executemany("""UPDATE feed_items SET read = 1 WHERE feed = ?""", feed_ids)
db.commit()
b.redirect(conf.prefix + "/unread")
@b.post(conf.prefix + "/mark_active")
@auth_required
def mark_active():
c, db = cursor()
feed_id = b.request.forms.get("feed_id")
active = b.request.forms.get("active")
c.execute("""UPDATE feeds SET active = ? WHERE id = ? """,
({"false": 0, "true": 1}[active], feed_id))
db.commit()
b.redirect(conf.prefix + "/feed/{}".format(feed_id))
@b.post(conf.prefix + "/edit_feed")
@auth_required
def edit_feed():
c, db = cursor()
feed_id = b.request.forms.get("feed_id")
name = b.request.forms.get("name")
feed_url = b.request.forms.get("feed_url")
site_url = b.request.forms.get("site_url")
c.execute("""UPDATE feeds SET name=?, feed_uri=?, site_uri=? WHERE id = ?""",
(name, feed_url, site_url, feed_id))
db.commit()
b.redirect(conf.prefix + "/feed/{}".format(feed_id))
@b.post(conf.prefix + "/add_feed")
@auth_required
def add_feed():
c, db = cursor()
name = b.request.forms.get("name")
feed_url = b.request.forms.get("feed_url")
site_url = b.request.forms.get("site_url")
c.execute("""INSERT INTO feeds VALUES (NULL,?,?,?,1)""",
(name, feed_url, site_url))
feed_id = c.lastrowid
db.commit()
b.redirect(conf.prefix + "/feed/{}".format(feed_id))
def run_refresh(feed_id = None):
global fetch_process
if fetch_process is None or fetch_process.poll() is not None:
cmd = ['./fetch_feeds.py']
if feed_id is not None:
cmd.extend(['--only-feed', feed_id, '--since', '0'])
fetch_process = subprocess.Popen(cmd)
@b.post(conf.prefix + "/refresh")
@auth_required
def refresh():
feed_id = b.request.forms.get("feed_id")
if feed_id == 'all':
feed_id = None
run_refresh(feed_id)
b.redirect(conf.prefix + "/unread")
def refresh_thread():
while True:
run_refresh()
time.sleep(1800)
@b.route(conf.prefix + "/css/<filename:path>")
def css(filename):
return b.static_file(filename, root="./css")
@b.route(conf.prefix + "/images/<filename:path>")
def imsages(filename):
return b.static_file(filename, root="./images")
setup_db.create_tables()
fetch_process = None
if conf.auto_fetch:
t = threading.Thread(target=refresh_thread)
t.daemon = True
t.start()
app = b.app()
if conf.compress:
app = gzip_middleware.Gzipper(app)
b.debug(conf.debug)
if conf.fastcgi:
b.run(app, host=conf.host, port=conf.port, server=b.FlupFCGIServer)
else:
b.run(app, host=conf.host, port=conf.port)