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.
|
sad vedle sebe.
|
||||||
|
|
||||||
Zálohovat i stěhovat jde prostým zkopírováním souboru; `DICE_DB` si ho
|
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
|
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
|
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>
|
<p class="eyebrow">Počitadlo</p>
|
||||||
<h1>Síň slávy</h1>
|
<h1>Síň slávy</h1>
|
||||||
</div>
|
</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>
|
</header>
|
||||||
|
|
||||||
{% if records.best_turn %}
|
{% if records.best_turn %}
|
||||||
|
|||||||
@@ -326,3 +326,36 @@ def test_qr_obdelniky_kryji_stejne_moduly():
|
|||||||
|
|
||||||
assert painted == expected
|
assert painted == expected
|
||||||
assert svg.count("<rect") < len(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.
|
Server-rendered HTML, formuláře přes POST/redirect/GET, žádný build step.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import io
|
||||||
import os
|
import os
|
||||||
|
import shutil
|
||||||
import socket
|
import socket
|
||||||
import sys
|
import sys
|
||||||
|
import tempfile
|
||||||
|
from datetime import date
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
import qrcode
|
import qrcode
|
||||||
from flask import (Flask, abort, g, redirect, render_template, request,
|
from flask import (Flask, abort, g, redirect, render_template, request,
|
||||||
url_for)
|
send_file, url_for)
|
||||||
from markupsafe import Markup, escape
|
from markupsafe import Markup, escape
|
||||||
from waitress import serve
|
from waitress import serve
|
||||||
|
|
||||||
@@ -319,6 +323,36 @@ def board_panel(game_id: int):
|
|||||||
return render_template("_panel.html", **view(game, game_id, row))
|
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")
|
@app.route("/stats")
|
||||||
def hall():
|
def hall():
|
||||||
conn = db()
|
conn = db()
|
||||||
|
|||||||
Reference in new issue
Block a user