add realtime update notification
This commit is contained in:
parent
f8eee5261a
commit
52a8564b97
6 changed files with 145 additions and 55 deletions
32
websocket_server.js
Normal file
32
websocket_server.js
Normal file
|
@ -0,0 +1,32 @@
|
|||
|
||||
var WebSocketServer = require('ws').Server;
|
||||
var Redis = require('redis');
|
||||
|
||||
var port = 8077;
|
||||
|
||||
var wss = new WebSocketServer({port: port});
|
||||
|
||||
wss.on('connection', function(ws) {
|
||||
// console.log("New websockets connection");
|
||||
ws.on('message', function(channel) {
|
||||
var redis = Redis.createClient(6379, 'localhost');
|
||||
redis.subscribe(channel);
|
||||
// console.log('Listening for comments on channel ' + channel);
|
||||
redis.on('message', function (channel, message) {
|
||||
console.log('Sent comment to channel ' + channel);
|
||||
ws.send(message);
|
||||
});
|
||||
ws.on('close', function(){
|
||||
// console.log('Killing listener for channel ' + channel);
|
||||
redis.unsubscribe();
|
||||
redis.end();
|
||||
});
|
||||
ws.on('error', function(){
|
||||
// console.log('Killing listener for channel ' + channel);
|
||||
redis.unsubscribe();
|
||||
redis.end();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
console.log("WebSocket Server Listening on port "+port);
|
|
@ -66,7 +66,5 @@ $(function(){
|
|||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
attachListeners();
|
||||
});
|
||||
|
|
23
woodwind/static/websocket_client.js
Normal file
23
woodwind/static/websocket_client.js
Normal file
|
@ -0,0 +1,23 @@
|
|||
|
||||
// topic will be woodwind::user:id or woodwind::feed:id
|
||||
function webSocketSubscribe(topic) {
|
||||
if ('WebSocket' in window) {
|
||||
|
||||
var ws = new WebSocket(window.location.origin
|
||||
.replace(/https?:\/\//, 'ws://')
|
||||
.replace(/(:\d+)?$/, ':8077'));
|
||||
|
||||
ws.onopen = function(event) {
|
||||
// send the topic
|
||||
console.log('subscribing to topic: ' + topic);
|
||||
ws.send(topic);
|
||||
};
|
||||
|
||||
ws.onmessage = function(event) {
|
||||
var data = JSON.parse(event.data);
|
||||
data.entries.forEach(function(entryHtml) {
|
||||
$('body main').prepend(entryHtml);
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
|
@ -1,11 +1,13 @@
|
|||
from config import Config
|
||||
from contextlib import contextmanager
|
||||
from woodwind.models import Feed, Entry
|
||||
from redis import StrictRedis
|
||||
import bs4
|
||||
import celery
|
||||
import celery.utils.log
|
||||
import datetime
|
||||
import feedparser
|
||||
import json
|
||||
import mf2py
|
||||
import mf2util
|
||||
import re
|
||||
|
@ -27,6 +29,8 @@ logger = celery.utils.log.get_task_logger(__name__)
|
|||
engine = sqlalchemy.create_engine(Config.SQLALCHEMY_DATABASE_URI)
|
||||
Session = sqlalchemy.orm.sessionmaker(bind=engine)
|
||||
|
||||
redis = StrictRedis()
|
||||
|
||||
|
||||
@contextmanager
|
||||
def session_scope():
|
||||
|
@ -65,7 +69,7 @@ def update_feed(feed_id):
|
|||
|
||||
def process_feed(session, feed):
|
||||
now = datetime.datetime.utcnow()
|
||||
found_new = False
|
||||
new_entries = []
|
||||
try:
|
||||
logger.info('fetching feed: %s', feed)
|
||||
response = requests.get(feed.feed)
|
||||
|
@ -102,14 +106,17 @@ def process_feed(session, feed):
|
|||
for in_reply_to in entry.get_property('in-reply-to', []):
|
||||
fetch_reply_context.delay(entry.id, in_reply_to)
|
||||
|
||||
found_new = True
|
||||
new_entries.append(entry)
|
||||
else:
|
||||
logger.info('skipping previously seen post %s', old.permalink)
|
||||
|
||||
finally:
|
||||
feed.last_checked = now
|
||||
if found_new:
|
||||
if new_entries:
|
||||
feed.last_updated = now
|
||||
session.commit()
|
||||
if new_entries:
|
||||
notify_feed_updated(session, feed, new_entries)
|
||||
|
||||
|
||||
def check_push_subscription(session, feed, response):
|
||||
|
@ -168,6 +175,28 @@ def check_push_subscription(session, feed, response):
|
|||
send_request('subscribe', hub, topic)
|
||||
|
||||
|
||||
def notify_feed_updated(session, feed, entries):
|
||||
"""Render the new entries and publish them to redis
|
||||
"""
|
||||
from . import create_app
|
||||
from flask import render_template
|
||||
import flask.ext.login as flask_login
|
||||
flask_app = create_app()
|
||||
|
||||
for user in feed.users:
|
||||
with flask_app.test_request_context():
|
||||
flask_login.login_user(user, remember=True)
|
||||
message = json.dumps({
|
||||
'user': user.id,
|
||||
'feed': feed.id,
|
||||
'entries': [
|
||||
render_template('_entry.jinja2', feed=feed, entry=e)
|
||||
for e in entries
|
||||
],
|
||||
})
|
||||
redis.publish('woodwind:user:{}'.format(user.id), message)
|
||||
|
||||
|
||||
def is_content_equal(e1, e2):
|
||||
"""The criteria for determining if an entry that we've seen before
|
||||
has been updated. If any of these fields have changed, we'll scrub the
|
||||
|
|
50
woodwind/templates/_entry.jinja2
Normal file
50
woodwind/templates/_entry.jinja2
Normal file
|
@ -0,0 +1,50 @@
|
|||
{% for context in entry.reply_context %}
|
||||
<article class="reply-context">
|
||||
<header>
|
||||
{% if context.author_photo %}
|
||||
<img src="{{context.author_photo}}"/>
|
||||
{% endif %}
|
||||
{% if context.author_name %}
|
||||
{{ context.author_name }} -
|
||||
{% endif %}
|
||||
{{ context.permalink | domain_for_url }}
|
||||
</header>
|
||||
{% if context.title %}
|
||||
<h1>{{ context.title }}</h1>
|
||||
{% endif %}
|
||||
{% if context.content %}
|
||||
<div>
|
||||
{{ context.content_cleaned() | add_preview }}
|
||||
</div>
|
||||
{% endif %}
|
||||
<footer>
|
||||
<a href="{{ context.permalink }}">{{ context.published }}</a>
|
||||
</footer>
|
||||
</article>
|
||||
{% endfor %}
|
||||
|
||||
<article>
|
||||
<header>
|
||||
{% if entry.author_photo %}
|
||||
<img src="{{entry.author_photo}}"/>
|
||||
{% endif %}
|
||||
{% if entry.author_name %}
|
||||
{{ entry.author_name }} -
|
||||
{% endif %}
|
||||
{{ entry.feed.name }}
|
||||
</header>
|
||||
{% if entry.title %}
|
||||
<h1>{{ entry.title }}</h1>
|
||||
{% endif %}
|
||||
{% if entry.content %}
|
||||
<div>
|
||||
{{ entry.content_cleaned() | add_preview }}
|
||||
</div>
|
||||
{% endif %}
|
||||
<footer>
|
||||
<a href="{{ entry.permalink }}">{{ entry.published }}</a>
|
||||
<div class="reply-area">
|
||||
{% include '_reply.jinja2' with context %}
|
||||
</div>
|
||||
</footer>
|
||||
</article>
|
|
@ -1,6 +1,7 @@
|
|||
{% extends "base.jinja2" %}
|
||||
{% block head %}
|
||||
<script src="{{url_for('static', filename='feed.js', version='2015-02-19')}}"></script>
|
||||
<script src="{{ url_for('static', filename='websocket_client.js', version='2015-02-22') }}"></script>
|
||||
|
||||
{% if current_user and current_user.settings
|
||||
and current_user.settings.get('reply-method') == 'indie-config' %}
|
||||
|
@ -21,56 +22,7 @@
|
|||
{% block body %}
|
||||
|
||||
{% for entry in entries %}
|
||||
{% for context in entry.reply_context %}
|
||||
<article class="reply-context">
|
||||
<header>
|
||||
{% if context.author_photo %}
|
||||
<img src="{{context.author_photo}}"/>
|
||||
{% endif %}
|
||||
{% if context.author_name %}
|
||||
{{ context.author_name }} -
|
||||
{% endif %}
|
||||
{{ context.permalink | domain_for_url }}
|
||||
</header>
|
||||
{% if context.title %}
|
||||
<h1>{{ context.title }}</h1>
|
||||
{% endif %}
|
||||
{% if context.content %}
|
||||
<div>
|
||||
{{ context.content_cleaned() | add_preview }}
|
||||
</div>
|
||||
{% endif %}
|
||||
<footer>
|
||||
<a href="{{ context.permalink }}">{{ context.published }}</a>
|
||||
</footer>
|
||||
</article>
|
||||
{% endfor %}
|
||||
|
||||
<article>
|
||||
<header>
|
||||
{% if entry.author_photo %}
|
||||
<img src="{{entry.author_photo}}"/>
|
||||
{% endif %}
|
||||
{% if entry.author_name %}
|
||||
{{ entry.author_name }} -
|
||||
{% endif %}
|
||||
{{ entry.feed.name }}
|
||||
</header>
|
||||
{% if entry.title %}
|
||||
<h1>{{ entry.title }}</h1>
|
||||
{% endif %}
|
||||
{% if entry.content %}
|
||||
<div>
|
||||
{{ entry.content_cleaned() | add_preview }}
|
||||
</div>
|
||||
{% endif %}
|
||||
<footer>
|
||||
<a href="{{ entry.permalink }}">{{ entry.published }}</a>
|
||||
<div class="reply-area">
|
||||
{% include '_reply.jinja2' with context %}
|
||||
</div>
|
||||
</footer>
|
||||
</article>
|
||||
{% include '_entry.jinja2' with context %}
|
||||
{% endfor %}
|
||||
|
||||
{% if entries %}
|
||||
|
@ -79,4 +31,10 @@
|
|||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if current_user %}
|
||||
<script>
|
||||
webSocketSubscribe('woodwind:user:' + {{ current_user.id }});
|
||||
</script>
|
||||
{% endif %}
|
||||
|
||||
{% endblock body %}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue