From ee2972cc3b82edb60c77abddda5b0ff23c9c9797 Mon Sep 17 00:00:00 2001 From: Mike Crute Date: Wed, 4 Jun 2014 00:50:25 -0700 Subject: Initial pass --- app.py | 209 +++++++++++++++++++++++++++++++++++++++++++++++++++ requirements.txt | 1 + templates/items.html | 183 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 393 insertions(+) create mode 100755 app.py create mode 100644 requirements.txt create mode 100644 templates/items.html diff --git a/app.py b/app.py new file mode 100755 index 0000000..755d55c --- /dev/null +++ b/app.py @@ -0,0 +1,209 @@ +#!/usr/bin/env python3 + +import sqlite3 +import base64 +from copy import copy +from datetime import datetime +from collections import defaultdict + +from flask import Flask, render_template, jsonify, request, make_response + +app = Flask(__name__) + + +class DataObject(object): + + @classmethod + def from_db_row(cls, cursor, row): + return cls(*row) + + def _update_json(self, data): + return + + def to_json(self): + data = copy(self.__dict__) + self._update_json(data) + data['self'] = self.self_link + return data + + def __repr__(self): + return '{self.__class__.__name__}({self.id!r}, {self.title!r})'.format( + self=self) + + @property + def self_link(self): + return self.URL.format(self=self) + + +class RSSItem(DataObject): + + URL = '/item/{self.id}' + + def __init__(self, id, title, author, feed_title, feed_url, url, + publish_date, content, read): + self.id = id + self.feed_url = RSSFeed(feed_url, None, None).self_link + self.feed_title = feed_title + self.title = title + self.author = author + self.url = url + self.publish_date = datetime.fromtimestamp(publish_date) + self.content = content + self.read = not bool(read) + self.no_content = len(content) < 100 + + +class RSSFeed(DataObject): + + URL = '/feed/{self.id}' + + def __init__(self, feed_url, url, title): + self.feed_url = feed_url + self.url = url + self.title = title + + def _update_json(self, data): + data['items'] = '/feed/{}/items'.format(self.id) + + @property + def id(self): + return base64.b64encode( + self.feed_url.encode('utf-8')).decode('utf-8').strip('=') + + @staticmethod + def parse_token(token): + return base64.b64decode( + '{}========'.format(token).encode('utf-8')).decode('utf-8') + + + +class DBReader(object): + + BASE_QUERY = ''' + SELECT + i.id, i.title, i.author, f.title, + f.rssurl, i.url, i.pubDate, i.content, + i.unread + FROM + rss_item i + INNER JOIN rss_feed f + ON f.rssurl = i.feedurl + {where} + {where_cond} + ORDER BY + i.pubDate desc + ''' + + def __init__(self, db_path): + self.db_path = db_path + + def _get_connection(self): + conn = sqlite3.connect(self.db_path) + conn.row_factory = RSSItem.from_db_row + return conn + + def _fetch(self, where=None, params=()): + with self._get_connection() as con: + curs = con.cursor() + args = { 'where': '', 'where_cond': '' } + + if where: + args.update({ 'where': 'WHERE', 'where_cond': where }) + + curs.execute(self.BASE_QUERY.format(**args), params) + return curs.fetchall() + + def update_unread(self, id, unread=True): + with self._get_connection() as con: + con.execute( + 'UPDATE rss_item SET unread = ? WHERE id = ?', + [1 if unread else 0, id]) + con.commit() + + def get_entry(self, id): + return self._fetch("i.id = ?", [id])[0] + + def get_unread(self): + data = defaultdict(list) + unread = self._fetch("i.unread = ?", [1]) + + for record in unread: + data[record.feed_title].append(record) + + return sorted(data.items()) + + def get_unread_for_feed(self, token, only_unread=False): + if only_unread: + return self._fetch("i.unread = ? AND i.feedurl = ?", [1, RSSFeed.parse_token(token)]) + else: + return self._fetch("i.feedurl = ?", [RSSFeed.parse_token(token)]) + + def get_feeds(self): + with self._get_connection() as con: + con.row_factory = RSSFeed.from_db_row + curs = con.cursor() + curs.execute('SELECT rssurl, url, title FROM rss_feed') + return curs.fetchall() + + def get_feed(self, token): + with self._get_connection() as con: + con.row_factory = RSSFeed.from_db_row + curs = con.cursor() + curs.execute('SELECT rssurl, url, title FROM rss_feed WHERE rssurl = ?', [RSSFeed.parse_token(token)]) + return curs.fetchall()[0] + + +def json_list(data): + return [i.to_json() for i in data] + + +@app.route('/') +def index(): + reader = DBReader('../../cache.db') + return render_template('items.html', items=reader.get_unread()) + + +@app.route('/feed/') +def feed_list(): + reader = DBReader('../../cache.db') + return jsonify({ 'feeds': json_list(reader.get_feeds()) }) + + +@app.route('/feed/') +def feed(token): + reader = DBReader('../../cache.db') + return jsonify(reader.get_feed(token).to_json()) + + +@app.route('/feed//items') +def feed_items(token): + reader = DBReader('../../cache.db') + return jsonify({ 'items': json_list(reader.get_unread_for_feed(token)) }) + + +@app.route('/feed//items/unread') +def unread_feed_items(token): + reader = DBReader('../../cache.db') + return jsonify({ 'items': json_list(reader.get_unread_for_feed(token, True)) }) + + +@app.route("/item/", methods=["GET", "POST"]) +def item(entry_id): + #post read=1 + reader = DBReader('../../cache.db') + + if request.method == 'POST': + try: + read = bool(int(request.form.get('read'))) + except: + return make_response('', 400) + + reader.update_unread(entry_id, not read) + return make_response('', 204) + else: + return jsonify(reader.get_entry(entry_id).to_json()) + + +if __name__ == '__main__': + app.debug = True + app.run() diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..632a1ef --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +Flask==0.10.1 diff --git a/templates/items.html b/templates/items.html new file mode 100644 index 0000000..093b211 --- /dev/null +++ b/templates/items.html @@ -0,0 +1,183 @@ + + + + RSS Reader + + + + + + + + + + +

RSS Entries

+ {% for feed, records in items %} +
+

{{ feed }} Mark All Read

+ +
+ {% endfor %} + + -- cgit v1.2.3