Send SSE
This commit is contained in:
parent
42cc2359a5
commit
cceee60939
@ -1,12 +1,26 @@
|
||||
from flask import render_template, redirect, request, flash, Blueprint, current_app, url_for
|
||||
from flask import render_template, redirect, request, flash, Blueprint, current_app, url_for, Response
|
||||
|
||||
import json
|
||||
|
||||
from .book import Book
|
||||
from . import isbn_db
|
||||
from . import sse
|
||||
|
||||
bp = Blueprint("app", __name__, url_prefix="/app")
|
||||
|
||||
announcer = sse.MessageAnnouncer()
|
||||
|
||||
def announce_book(book, msg_type: str="add_book") -> None:
|
||||
"""
|
||||
msg_type must either be "update_book", "add_book" or "delete_book"
|
||||
"""
|
||||
msg = sse.format_sse({
|
||||
"type": msg_type,
|
||||
"book": book.to_json()
|
||||
})
|
||||
announcer.announce(msg)
|
||||
|
||||
|
||||
@bp.route("/submit-isbn")
|
||||
def submit_isbn():
|
||||
if "isbn" not in request.args:
|
||||
@ -24,18 +38,23 @@ def submit_isbn():
|
||||
book.load()
|
||||
except KeyError:
|
||||
isbn_db.add_book(book)
|
||||
announce_book(book)
|
||||
return "Pas trouvé, à rajouter manuellement"
|
||||
|
||||
print("Got ", book)
|
||||
if isbn_db.add_book(book) == "duplicate":
|
||||
announce_book(book, msg_type="update_book")
|
||||
return f"{book.title} ajouté (plusieurs occurrences)"
|
||||
announce_book(book)
|
||||
return f"{book.title} ajouté"
|
||||
|
||||
|
||||
@bp.route("/web-submit-isbn")
|
||||
def web_submit_isbn():
|
||||
flash(submit_isbn())
|
||||
return redirect(url_for("app.index"))
|
||||
|
||||
|
||||
@bp.route("/")
|
||||
def index():
|
||||
return render_template("index.html", books=isbn_db.get_all_books())
|
||||
@ -47,8 +66,16 @@ def delete_book():
|
||||
return "missing isbn"
|
||||
|
||||
isbn_db.delete_book(request.form["isbn"])
|
||||
msg = sse.format_sse({
|
||||
"type": "delete_book",
|
||||
"book": {
|
||||
"isbn": request.form["isbn"]
|
||||
}
|
||||
})
|
||||
announcer.announce(msg)
|
||||
return redirect(url_for("app.index"))
|
||||
|
||||
|
||||
@bp.route("/update-book", methods=["POST"])
|
||||
def update_book():
|
||||
attributes = ["isbn", "count", "title", "author", "publisher", "publish_date"]
|
||||
@ -70,8 +97,10 @@ def update_book():
|
||||
count=int(form_data["count"])
|
||||
)
|
||||
isbn_db.update_book(book)
|
||||
announce_book(book, msg_type="update_book")
|
||||
return redirect(url_for("app.index"))
|
||||
|
||||
|
||||
@bp.route("/export-csv")
|
||||
def export_csv():
|
||||
books = isbn_db.get_all_books()
|
||||
@ -86,3 +115,21 @@ def export_csv():
|
||||
mimetype="text/csv",
|
||||
headers={"Content-Disposition": "attachment;filename=books.csv"}
|
||||
)
|
||||
|
||||
|
||||
@bp.route('/listen', methods=['GET'])
|
||||
def listen():
|
||||
def stream():
|
||||
messages = announcer.listen() # returns a queue.Queue
|
||||
while True:
|
||||
msg = messages.get() # blocks until a new message arrives
|
||||
yield msg
|
||||
|
||||
return Response(stream(), mimetype='text/event-stream')
|
||||
|
||||
|
||||
@bp.route('/ping')
|
||||
def ping():
|
||||
msg = sse.format_sse(data={"type": "pong"})
|
||||
announcer.announce(msg=msg)
|
||||
return {}, 200
|
@ -58,3 +58,13 @@ class Book:
|
||||
if self.title is None:
|
||||
return f"<Book: ISBN:{self.isbn}>"
|
||||
return f"<Book: {self.title}>"
|
||||
|
||||
def to_json(self):
|
||||
return {
|
||||
"isbn": self.isbn,
|
||||
"title": self.title,
|
||||
"publisher": self.publisher,
|
||||
"publish_date": self.publish_date,
|
||||
"author": self.author,
|
||||
"count": self.count
|
||||
}
|
26
isbn_sort/sse.py
Normal file
26
isbn_sort/sse.py
Normal file
@ -0,0 +1,26 @@
|
||||
import queue
|
||||
import json
|
||||
|
||||
class MessageAnnouncer:
|
||||
def __init__(self):
|
||||
self.listeners = []
|
||||
|
||||
def listen(self):
|
||||
q = queue.Queue(maxsize=5)
|
||||
self.listeners.append(q)
|
||||
return q
|
||||
|
||||
def announce(self, msg):
|
||||
for i in reversed(range(len(self.listeners))):
|
||||
try:
|
||||
self.listeners[i].put_nowait(msg)
|
||||
except queue.Full:
|
||||
del self.listeners[i]
|
||||
|
||||
|
||||
def format_sse(data: str, event=None) -> str:
|
||||
json_data = json.dumps(data)
|
||||
msg = f'data: {json_data}\n\n'
|
||||
if event is not None:
|
||||
msg = f'event: {event}\n{msg}'
|
||||
return msg
|
23
isbn_sort/static/dynamicUpdate.js
Normal file
23
isbn_sort/static/dynamicUpdate.js
Normal file
@ -0,0 +1,23 @@
|
||||
const listener = new EventSource("/app/listen");
|
||||
|
||||
listener.onmessage = (e) => {
|
||||
let data = JSON.parse(e.data);
|
||||
console.log(data);
|
||||
|
||||
switch (data.type) {
|
||||
case "pong":
|
||||
console.log("pong!");
|
||||
break;
|
||||
case "add_book":
|
||||
viewAddBook(data.book);
|
||||
break;
|
||||
case "update_book":
|
||||
viewUpdateBook(data.book);
|
||||
break;
|
||||
case "delete_book":
|
||||
viewDeleteBook(data.book);
|
||||
break;
|
||||
default:
|
||||
console.log("Unexpected response from SSE:", data);
|
||||
}
|
||||
};
|
@ -1,4 +1,4 @@
|
||||
getBookData = (isbn) => {
|
||||
function getBookData(isbn) {
|
||||
var table = document.getElementById("books-table");
|
||||
|
||||
// Find all rows in the table except the header
|
||||
@ -32,7 +32,7 @@ getBookData = (isbn) => {
|
||||
return null;
|
||||
}
|
||||
|
||||
function edit_book(isbn) {
|
||||
function openEditBookDialog(isbn) {
|
||||
var bookData = getBookData(isbn);
|
||||
if (bookData) {
|
||||
var editDialog = document.getElementById("edit-book-dialog");
|
||||
@ -55,7 +55,7 @@ function hideEditBookDialog() {
|
||||
}
|
||||
|
||||
// Function to show the delete-book dialog
|
||||
function delete_book(isbn) {
|
||||
function openDeleteBookDialog(isbn) {
|
||||
var bookData = getBookData(isbn);
|
||||
console.log(isbn, bookData)
|
||||
if (bookData === null)
|
||||
|
@ -11,7 +11,6 @@
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
|
||||
</head>
|
||||
<body>
|
||||
<script src="{{ url_for('static', filename='main.js') }}" defer></script>
|
||||
<div id="container">
|
||||
{% for message in get_flashed_messages() %}
|
||||
<div class="flash">{{ message }}</div>
|
||||
|
@ -7,6 +7,8 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<script src="{{ url_for('static', filename='main.js') }}" defer></script>
|
||||
<script src="{{ url_for('static', filename='dynamicUpdate.js') }}" defer></script>
|
||||
<dialog id="edit-book-dialog">
|
||||
<form id="edit-book-form" action="/app/update-book" method="post">
|
||||
<input type="hidden" id="edit-isbn" value="" name="isbn">
|
||||
@ -60,8 +62,8 @@
|
||||
<td><p {% if book.publisher == None %}class="red"{% endif %}>{{ book.publisher }}</p></td>
|
||||
<td><p {% if book.count != 1 %}class="red"{% endif %}>{{ book.count }}</p></td>
|
||||
<td>
|
||||
<button class="action" onclick="edit_book({{ book.isbn }})">✏️</button>
|
||||
<button class="action" onclick="delete_book({{ book.isbn }})">🗑️</button>
|
||||
<button class="action" onclick="openEditBookDialog({{ book.isbn }})">✏️</button>
|
||||
<button class="action" onclick="openDeleteBookDialog({{ book.isbn }})">🗑️</button>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
Loading…
Reference in New Issue
Block a user