diff --git a/isbn_sort/app.py b/isbn_sort/app.py index 60228ed..10a0d2b 100644 --- a/isbn_sort/app.py +++ b/isbn_sort/app.py @@ -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() @@ -85,4 +114,22 @@ def export_csv(): csv, mimetype="text/csv", headers={"Content-Disposition": "attachment;filename=books.csv"} - ) \ No newline at end of file + ) + + +@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 \ No newline at end of file diff --git a/isbn_sort/book.py b/isbn_sort/book.py index cae391c..f35cbd6 100644 --- a/isbn_sort/book.py +++ b/isbn_sort/book.py @@ -57,4 +57,14 @@ class Book: def __repr__(self): if self.title is None: return f"" - return f"" \ No newline at end of file + return f"" + + 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 + } \ No newline at end of file diff --git a/isbn_sort/sse.py b/isbn_sort/sse.py new file mode 100644 index 0000000..c0d733b --- /dev/null +++ b/isbn_sort/sse.py @@ -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 diff --git a/isbn_sort/static/dynamicUpdate.js b/isbn_sort/static/dynamicUpdate.js new file mode 100644 index 0000000..92d9ce5 --- /dev/null +++ b/isbn_sort/static/dynamicUpdate.js @@ -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); + } +}; \ No newline at end of file diff --git a/isbn_sort/static/main.js b/isbn_sort/static/main.js index f0ee094..ed60133 100644 --- a/isbn_sort/static/main.js +++ b/isbn_sort/static/main.js @@ -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) diff --git a/isbn_sort/templates/base.html b/isbn_sort/templates/base.html index b898d9a..1fc8975 100644 --- a/isbn_sort/templates/base.html +++ b/isbn_sort/templates/base.html @@ -11,7 +11,6 @@ -
{% for message in get_flashed_messages() %}
{{ message }}
diff --git a/isbn_sort/templates/index.html b/isbn_sort/templates/index.html index 7b19c28..344cc28 100644 --- a/isbn_sort/templates/index.html +++ b/isbn_sort/templates/index.html @@ -7,6 +7,8 @@ {% endblock %} {% block content %} + +
@@ -60,8 +62,8 @@

{{ book.publisher }}

{{ book.count }}

- - + + {% endfor %}