Stažení databáze ze síně slávy
Dokud se hrálo ze zdrojáků, ležel dice.db v pracovním adresáři a záloha byla otázka zkopírování. Z binárky se schovává do systémového datového adresáře, který na Windows běžný člověk nenajde — takže se teď dá stáhnout přes prohlížeč. VACUUM INTO udělá konzistentní kopii i uprostřed zápisu, na rozdíl od prostého přečtení souboru. Kopie se přečte do paměti a hned zahodí, ať nezůstane ležet v dočasném adresáři, když si ji někdo nestáhne celou.
This commit is contained in:
1 parent
1a2053ff66
commit
a655bfe6f3
4 files changed
+73
-3
No files matched your search
@@ -99,7 +99,9 @@ napevno domovský adresář. Ze zdrojáků zůstává relativní, ať se dá mí
|
||||
sad vedle sebe.
|
||||
|
||||
Zálohovat i stěhovat jde prostým zkopírováním souboru; `DICE_DB` si ho
|
||||
najde kdekoli.
|
||||
najde kdekoli. Kdo ho hledat nechce — a z binárky na Windows je zalezlý —
|
||||
si ho stáhne ze síně slávy odkazem **Stáhnout databázi** (`/export`). Kopie
|
||||
se dělá přes `VACUUM INTO`, takže je použitelná i uprostřed rozehrané hry.
|
||||
|
||||
Schéma má verzi uloženou v `PRAGMA user_version` a `storage.migrate()` si
|
||||
databázi při každém připojení dorovná. Starší soubory z verzí, kdy se verze
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
<p class="eyebrow">Počitadlo</p>
|
||||
<h1>Síň slávy</h1>
|
||||
</div>
|
||||
<p class="meta"><a href="{{ url_for('index') }}">Nová hra</a></p>
|
||||
<p class="meta"><a href="{{ url_for('index') }}">Nová hra</a>
|
||||
· <a href="{{ url_for('export') }}">Stáhnout databázi</a></p>
|
||||
</header>
|
||||
|
||||
{% if records.best_turn %}
|
||||
|
||||
@@ -326,3 +326,36 @@ def test_qr_obdelniky_kryji_stejne_moduly():
|
||||
|
||||
assert painted == expected
|
||||
assert svg.count("<rect") < len(expected)
|
||||
|
||||
|
||||
# --- záloha -----------------------------------------------------------------
|
||||
|
||||
def test_export_vrati_pouzitelnou_databazi(client, tmp_path):
|
||||
"""Ze staženého souboru musí jít data přečíst zpátky, ne jen že se stáhne."""
|
||||
game_id = start_game(client, "Adam", "Eva")
|
||||
score(client, game_id, 150)
|
||||
|
||||
response = client.get("/export")
|
||||
|
||||
assert response.status_code == 200
|
||||
assert "attachment" in response.headers["Content-Disposition"]
|
||||
assert response.headers["Content-Disposition"].endswith(".db")
|
||||
|
||||
stazeny = tmp_path / "stazeny.db"
|
||||
stazeny.write_bytes(response.data)
|
||||
|
||||
conn = storage.connect(stazeny)
|
||||
try:
|
||||
assert [p["name"] for p in storage.seating(conn, game_id)] == ["Adam", "Eva"]
|
||||
assert storage.load_game(conn, game_id).totals()["Adam"] == 150
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
def test_export_zabere_i_rozehranou_hru(client):
|
||||
"""Kopie se dělá za běhu, uprostřed nedohrané hry."""
|
||||
game_id = start_game(client, "Adam")
|
||||
score(client, game_id, 100)
|
||||
|
||||
assert client.get("/export").status_code == 200
|
||||
assert text(client.get("/stats")).count("Stáhnout databázi") == 1
|
||||
@@ -8,14 +8,18 @@ Telefon zapisuje skóre (`/game/<id>`), notebook ukazuje zápisník (`/board`).
|
||||
Server-rendered HTML, formuláře přes POST/redirect/GET, žádný build step.
|
||||
"""
|
||||
|
||||
import io
|
||||
import os
|
||||
import shutil
|
||||
import socket
|
||||
import sys
|
||||
import tempfile
|
||||
from datetime import date
|
||||
from pathlib import Path
|
||||
|
||||
import qrcode
|
||||
from flask import (Flask, abort, g, redirect, render_template, request,
|
||||
url_for)
|
||||
send_file, url_for)
|
||||
from markupsafe import Markup, escape
|
||||
from waitress import serve
|
||||
|
||||
@@ -319,6 +323,36 @@ def board_panel(game_id: int):
|
||||
return render_template("_panel.html", **view(game, game_id, row))
|
||||
|
||||
|
||||
@app.route("/export")
|
||||
def export():
|
||||
"""Databáze ke stažení.
|
||||
|
||||
Dokud se hrálo ze zdrojáků, ležel `dice.db` v pracovním adresáři a záloha
|
||||
byla otázka zkopírování. Z binárky se schovává do systémového datového
|
||||
adresáře, který na Windows běžný člověk nenajde — tak ať se dá stáhnout
|
||||
přes prohlížeč.
|
||||
|
||||
`VACUUM INTO` udělá konzistentní kopii i uprostřed zápisu, na rozdíl od
|
||||
prostého přečtení souboru. Výsledek si přečteme do paměti a kopii hned
|
||||
zahodíme: databáze počitadla se počítá v kilobajtech a takhle nezůstane
|
||||
ležet v dočasném adresáři, když si ji někdo nestáhne celou.
|
||||
"""
|
||||
conn = db()
|
||||
conn.commit() # VACUUM nesmí běžet v transakci
|
||||
|
||||
docasny = tempfile.mkdtemp()
|
||||
try:
|
||||
kopie = Path(docasny) / "dice.db"
|
||||
conn.execute("VACUUM INTO ?", (str(kopie),))
|
||||
data = kopie.read_bytes()
|
||||
finally:
|
||||
shutil.rmtree(docasny, ignore_errors=True)
|
||||
|
||||
return send_file(io.BytesIO(data), mimetype="application/vnd.sqlite3",
|
||||
as_attachment=True,
|
||||
download_name=f"kostky-{date.today().isoformat()}.db")
|
||||
|
||||
|
||||
@app.route("/stats")
|
||||
def hall():
|
||||
conn = db()
|
||||
|
||||
Reference in new issue
Block a user